Понятие указателя. Адресная арифметика
В языке С++ существует два способа доступа к переменной: обращение к переменной по имени и использование механизма указателей.
Указатель-переменная (или просто указатель) – это переменная, предназначенная для хранения адреса в памяти.
Указатель-константа – это значение адреса оперативной памяти. В языке С++ определены две специальные операции для доступа к переменным через указатели: операция & и операция *. Результатом операции & является адрес объекта, к которому операция применяется. Например, &var1 дает адрес, по которому var1 хранится в памяти (точнее, адрес первого байта var1). Операция * - это операция обращения к содержимому памяти по адресу, хранимому в переменной-указателе или равному указателю-константе.
Признаком переменной-указателя для компилятора является наличие в описании переменной двух компонентов:
типа объекта данных, для доступа к которому используется указатель (т.е. на который ссылается указатель);
символа * перед именем переменной.
В совокупности тип и * воспринимаются компилятором как особый тип данных – «указатель на что-либо». Таким образом, описание
int var1, *ptr;
приводит к появлению переменной var1 и указателя-переменной ptr. Переменная var1 будет занимать два байта памяти. Указатель ptr имеет тип int* т.е. тип «указатель на целое». Место, выделяемое под такой тип компилятором, зависит от модели памяти. Указатели при их описании могут, как и обычные переменные, получать начальное значение. Например:
int var1, ptr1 = (int*)200, *ptr2 = &var1;
Здесь описаны две переменные-указатели ptr1 и ptr2; ptr1 получает начальное значение 200, а ptr2 в качестве начального значения – адрес, по которому в памяти хранится var1.
Операцию * можно выразить словами: «взять содержимое по адресу, равного значению указателя». Например, оператор присваивания
*ptr = *ptr2 + 4;
можно интерпретировать так: взять содержимое памяти по адресу, равному значению указателя ptr2, прибавить к этому содержимому 4, а результат поместить по адресу, равному значению указателя ptr1. Число байтов, извлекаемых из памяти и участвующих в операции, определяется компилятором исходя из типа, на который указывает указатель. Запись типа
*234 = var1;
будет ошибкой, т.к. в левой части оператора присваивания записывается адрес константы, а константа в С++ не имеет адреса в памяти.
Существуют ограничения и на использование операции взятия адреса &:
нельзя определить адрес константы, например некорректным является
выражение
var1 = &0xff00;
2) нельзя определить адрес значения, получаемого при выполнении арифметического выражения, включающего знаки +, -, /, * и т.п. Например, некорректным является выражение
int var1, *ptr;
ptr = &(var1*3);
3) нельзя определить адрес переменной, описанной как register. Например, будет ошибкой попытка определить адрес var1:
unsigned register var1;
unsigned int *ptr;
ptr = &var1;
Сам указатель-переменная тоже имеет адрес. Поэтому, например, корректным будет такой фрагмент:
int var1, *ptr1, *ptr2 = &var1;
ptr1 = (int*)&ptr2;
Здесь описываются два указателя-переменные и ptr2 инициализируется значением адреса переменной var1. Затем ptr1 присваивается значение адреса, по которому в памяти располагается ptr2.
Указатель типа void* называют часто родовым (generic). Ключевое слово void говорит об отсутствии данных о размере объекта в памяти. Но компилятору для корректной интерпретации ссылки на память через указатель нужна информация о числе байтов, участвующих в операции. Поэтому во всех случаях использования указателя, описанного как void*, необходимо выполнить операцию явного приведения типа указателя. Например:
unsigned long block = 0xffeeddccL;
void *ptr = █
char ch;
unsigned two_bytes;
long int four_bytes;
ch = *(char*) ptr; /* ch = 0xcc; */
two_bytes = *(unsigned*) ptr; /* two_bytes = 0xddcc; */
four_bytes = *(long int*) ptr; /* four_bytes = 0xffeeddcc; */
В комментариях приведены значения переменных, полученных в результате присваивания. Напомним, что младший байт всегда располагается в памяти по меньшему адресу.
Для указателей-переменных разрешены некоторые операции: присваивание; инкремент или декремент; сложение или вычитание; сравнение.
Язык С++ разрешает операцию сравнения указателей одинакового типа. При выполнении присваивания значение указателя в правой части выражения пересылается в ячейку памяти, отведенную для указателя в левой части.
Важной особенностью арифметических операций с указателями является то, что физическое увеличение или уменьшение его значения зависит от типа указателя, т.е. от размера того объекта, на который указатель ссылается. Если к указателю, описанному как type*ptr; прибавляется или отнимается константа N, значение ptr изменяется на N*sizeof(type). Разность двух указателей type*ptr1, *ptr2 – это разность их значений, поделенная на sizeof(type).
В частности, арифметические операции над указателями типа char* (размер типа равен 1) выполняются как над обычными целыми числами с той лишь разницей, что значения, участвующие в операции, - это адреса в оперативной памяти. Однако для других типов указателей это не так. Например:
#include <stdio.h>
void main(void)
{
int near*ptr1 = (int*)100;
int near*ptr2 = (int*)200;
ptr1 ++; ptr2 -= 10;
printf("ptr2 = %d, ptr1 = %d, ptr2 – ptr1 = %d\n", ptr2, ptr1, ptr2 – ptr1);
}
Так как указатель имеет тип int* (длина типа 2 байта), то «единица изменения» указателя и «единица измерения разности» равны двум байтам. Для других типов указателей такие же вычисления дают следующий результат:
для long* и float*
ptr2 = 160, ptr1 = 104, ptr2 – ptr1 = 14,
для double*
ptr2 = 120, ptr1 = 108, ptr2 – ptr1 = 1,
для long double*
ptr2 = 100, ptr1 = 110, ptr2 – ptr1 = -1.
Такие правила арифметических операций с указателями вытекают из того, что указатель в Си неявно рассматривается как указатель на начало массива однотипных элементов. Продвижение указателя вперед или назад совпадают с увеличением или уменьшением индекса элемента.
Дата добавления: 2017-01-29; просмотров: 1363;