Указатели в СИ++

1.Основные понятия об указателях

Язык Си отличается от других структурированных немашинных языков широким использованием указателей. В определённой степени именно наличие в языке Си указателей сделало его очень удобным для системного программирования. Указатели обеспечивают доступ к адресам переменных в памяти. Они позволяют работать с символическими адресами (именами переменных, содержащих адреса) и выполнение над адресами ряда операций. С помощью указателей например можно:

Применение указателей часто критикуется из-за того, что в силу их природы невозможно определить, на что (на какую переменную) указывает в данный момент указатель, если не возвращаться к тому месту, где указателю в последний раз было присвоено значение. Это усложняет программу и делает её правильность сомнительной.

Программист, знающий Си, должен прежде всего знать, что такое указатели, и уметь их испльзовать.

Указатель-это переменная или константа, которая содержит значение адреса другой переменной.

Данное изречение поясняется РИС.1

 

Рисунок1. Графическая интерпретация указателя

 

 

Никогда не следует использовать неинициализированные указатели, потому что это приводит к ошибкам, которые весьма трудно поддаются отладке.Инициализация указателей более актуальна, чем переменных, так как поведение программы, имеющей неопределённые указатели, непредсказуемо и часто вызывает зависание системы.

 

2.Объявление указателей и основные операции над ними

Указатель-это переменная или константа стандартного типа данных для хранения адреса переменной определённого типа. Тип адресуемой переменной может быть стандартный, перечислимый, структурный, объединение или void. Указатель на тип void может адрес.

Форма объявления переменной типа указатель:

тип [модификатор] *<имя-указателя>

где :

тип-имя типа переменной, адрес которой будет содержать переменная- указатель.(например integer, char, long)

имя-указателя –идентификатор переменной типа указатель.(имя собственное)

*- определяет переменную типа указатель.

 

Значение переменной-указателя-это адрес некоторой величины, целое без знака. При выводе значения указателя надо использовать формат %u. Указатель содержит адрес первого байта переменной определённого типа. Тип адресуемой переменной, на которую ссылается указатель, определяет объём оперативной памяти, выделяемой переменной, связанной с указателем. Для того, что бы машинной программой обработать (например прочитать или записать) значение переменной с помощью указателя, надо знать адрес её начального (нулевого) байта и количество байтов, которая занимает эта переменная. Ну и указатель естественно содержит эти данные:

Сам указатель содержит адрес нулевого байта этой переменной, а тип адресуемой переменной определяет, сколько байтов, начиная с нулевого (адреса, определённого указателем) занимает это значение.

Примеры объявлений даны на РИС.2

Рисунок2. Примеры объявлений указателей

Язык Си даёт возможность использования адресов переменных программы с помощью основных операций: & и *

&-получение адреса переменной.

*-извлечение значения, расположенного по этому адресу.

С помощью основных операций можно получить значение адреса переменной и использовать косвенную адресацию-получение значения переменной по её адресу.

Операции * и & можно писать вплотную к имени операнда или через пробел.Например: &i, *ptri.

Назначение этих операций:

&-имя переменной-получение адреса, определяет адрес размещения значения переменной определённого типа. Операндом операции & должно быть имя переменной того же типа, для которой определён и указатель левой части оператора присваивания, получающий значение этого адреса.

*-имя указателя-получение значения определённого типа по заданному адресу. Определяет содержимое, находящееся по адресу, который содержится в указателе-переменной или указателе-константе. Иначе: косвенная адресация. Косвенная адресация значения с помощью операции * осуществляет доступ к значению по указателю, то есть извлечение значения, расположенного по адресу-содержимому указателя. Операнд *(т.е имя после этого значка) должно быть типа указатель(где-то раньше объявлено).

Оператор присваивания значения адреса указателю(иначе инициализация указателя) имеет вид:

имя указателя_переменной=&имя_переменной

Например: int *ptri,i; //объявление указателя и переменной типа int

ptri=&i; //ptri получает значение адреса ‘i’

В общем виде оператор присваивания, использующий имя указателя и * операцию косвеной адресации, можно представить в виде:

Имя_переменной=*имя_указателя

Где имя-указателя –это переменная или константа, которая содержит адрес размещаемого значения, требуемого для переменной левой части оператора присваивания.

Например: i=*ptri; // ‘i’ получает значение,расположенное по адресу

// содержащемся в указателе‘ptri’

Как и любые переменные, переменная типа указатель ptri имеет адрес и значение.

Схематично взаимосвязь между указателям и адресуемым значением представлениа на РИС.3

Рисунок 3.Взаимосвязь указателя, адреса и значения переменной

 

Указатели можно использовать:

*ptri-значение переменной, находящейся по адресу, содержащемуся в указателяе ptri

ptri-значение адреса переменной

&ptri-адрес местоположения самого указателя

 

Указатель на переменную можно использовать в любом месте программы, где используется переменная, вместо этой переменной, например:

int i=123, j, *ptri; //объявление переменных и указателя

ptri=&i; //инициализация указателя(присвоение адреса i)

j=*ptri+1; //переменной i (*ptri) присваивается значение //переменной i и к её содержимому прибавляется единичка.

Следует отметить, что операции & и * более приоритетны, чем арифметические операции

3.Многоуровневая адресация

В языке Си можно использовать многоуровневую косвеную адресация, то есть косвеную адресация 1,2 и т.д. уровней. При этом для объявления и обращения к значениям с помощью указателей можно использовать соответственно несколко символов звёздочка *.Звёздочки при объявлении как бы уточняют назначение имени переменной, определяя уровень косвеной адресации для обращения к значениям с помощью этих указателей.Пример объявления переменной и указателей для многоуровневой косвеной адресации значений дан ниже:

int i=123; //где i-имя переменной

int *pi=&i; //pi –указатель на переменную

int **ppi=&pi; //ppi-указатель на указатель на переменную

int ***pppi=&ppi; //pppi-указатель на указатель на указатель на переменную’’.

Для обращения к значениям с помощью указателей можно применять следующее правило, жёстко связывающее форму обращения с объявлением этих указателей:

Пример, поясняющий это правило дан на РИС.4

Рисунок 4.Соответствие между количеством уточнений (*) и результатом обращения к значению с помощью указателя.

Где У.К.А.-уровень косвеной адресации

 

Пример, иллюстрирующий многоуровневую косвенную адресацию

 

4.Операции над указателями

Язык Си предоставляет возможности для выполнениянад указателями операций присваивания, целочисленой арифметики и сравнений.

На языке Си можно:

  • Присвоить указателю значение адреса данных, или нуль.
  • Увеличить (уменьшить) значение указателя
  • Прибавить (вычесть) из значения указателя целое число
  • Сложить или вычесть значение одного указателя из другово
  • Сравнить два указателя с помощью операций отношения.

Переменной-указателю можно задать значение с помощью одного из способов:

  • Присвоить указателю адрес переменной, имеющей место в оперетивной памяти, или нуль, например: ptri=&i; ptri=NULL;
  • Объявить указатель вне функции (в том числе main) либо в любой функции, снабдив его описателем stastic, при этом начальным значением указателя является нулевой адрес (NULL)
  • Присвоить указателю значение другово указателя, который к этому времени уже инициализирован (имеет определённое значение), например: ptri=ptrj; -это двойное указание одной и той же переменной.
  • Присвоить переменной-указателю значение с помощью функций malloc и calloc.

Изменение значений указателя можно производить с помощью операций +, ++, -, --. Бинарные операции (+ и -) можно выполнять над указателями, если оба указателя ссылаются на переменные одного типа, так как объём оперативной памяти для различных типов данных может быть разный. Например, значение типа char занимает 1(один) байт, значение типа int занимает 2(два) байта, а под значение типа float выделяется аж 4(четыре) байта. Добавление 1 к указателю добавит квант памяти,то есть количество байтов, которое занимает одно значение адресуемого типа. Для указателя на элементы массива это означает, что осуществляется переход к адресу следующего элемента массива, а не следующего байта. То есть значение указателя при переходе от элемента к элементу массива целых чисел будет увеличиваться на 2, а типа float-на 4.Более подробно это продемонстрированно на РИС.5

Рисунок 5.Арифметические действия над различными типам указателей.

 

В языке Си++ связь между указателями и массивами настолько тесная, что программисты обычно предпочитают использовать указатели при работе с массивами. Поскольку массивы являются некоторым аналогом указателей, язык Си позволяет программам разыминовывать имена массивов с помощью такого выражения как *имя_массива, например:

int mas[10],*ptrm;

ptrm=&mas[0];

*prtm==mas[0]==*(mas+0) ; -значение нулевого элемента массива mas

*(ptrm+i)==mas[i]==*(mas+i); --значение i-го элемента массива mas

А операции над элементами массива mas можно представить в виде:

*mas+2==mas[0]+2; *(mas+i)-3==mas[i]-3;

А вот задачка, предложенная на собеседовании в одной софтверной фирме:

*(&(mas[i+1])+2)++;

  • ptrm==&mas[i+1]; упрощаем данное выражение, i здесь не играет роли
  • ptrm+2==&(mas[i+1])+2; здесь указатель переводится на 2 элемента вперёд
  • *ptrm++==(*ptrm=*ptrm+1); здесь содержимое ячейки массива извлекается и к нему прибавляется единичка

Если указатель имеет префиксные (слева от имени указателя) и постфиксные (справа от имени указателя) операции, то они выполняются в следующей последовательности:

  • Префиксные операции в последовательности спарава налево.
  • Использование значения, полученного после выполнения префиксных операций
  • Постфиксная операция над указателем.

Например в выражении *p++ сначала выполняется префиксная операция над указателем ,то есть определяется значение *p-содержимое, расположенное по адресу px, а затем выполняется посфиксная операция ++ увелечение значения указателя на квант памяти, то есть на 2 байта (если указатель типа int)

А, например в выражениии (++(*p)+2) сначала:

  • *p -так как префиксные операции выполняются справа налево.
  • *p=*p+1 -самая левая префиксная операция
  • +2 -выполнение посфиксной операции

 

5.Проблеммы, связанные с указателями

Проблеммы, связанные с указателями, возникают при некорректном использовании этих указателей. Некорректным использованием указателей может быть:

  • Попытка работать с неинициализированными указателям, то есть с указателем, не содержащим адреса оперативной памяти (ОП), выделеной переменной.
  • Потеря ссылки, то есть значения указателя из-за присваивания ему нового значения до освобождения ОП, которою он адресует.
  • Неосвобождение ОП, запрошенной с помощью функции malloc

Попытка возвратить в качестве результата работы функции адрес локальной переменной (класса auto).

Назад

Hosted by uCoz