Указатель - это переменная, содержащая адрес. Адрес другой переменной, адрес массива, структуры или его/её части, адрес выделенного куска памяти, адрес машинной инструкции, библиотеки… просто какой-то левый адрес, даже если по нему нельзя писать или/и читать.
Указатели применяются для косвенного доступа к переменным или другим данным. Иногда обратиться к переменной по её имени не представляется возможным (в т.ч. из-за ограничения области видимости - например, когда она объявлена в другой функции), в этом случае можно попытаться добраться до неё по адресам, с помощью указателей и адресной арифметики. Или переменная/объект вовсе не имеет имени - когда память под него была выделена динамически. Есть и другие случаи применения указателей: например, строки, списки, деревья, графы в Си построены на указателях. Ну и более специфические случаи
Размер переменной-указателя равен размеру машинного слова (4 байта в 32-битных системах, 8 байт в 64-битных, 2 байта в 16-битных (если near-указатели)).
Синтаксис:
type *name;
где type имя типа переменной, на которую указывает указатель. Если описывается сразу несколько указателей, то надо перед каждым ставить звёздочку:
int *p1, *p2;
иначе мы опишем один указатель и одну целую переменную (именно поэтому принято пробел ставит перед а не после, как было бы логичнее; но компилятору всё равно, хоть вообще без пробелов).
Указатель можно “настроить” на какую-нибудь переменную с помощью операции взятия адреса ”&”:
int a = 5;
int *p = &a; // можно и разделить описание и инициализацию: int *p; p = &a;
и затем с помощью операции разыменования указателя читать или писать значение из/в переменную a:
*p = 6; // теперь a == 6
a = 7;
printf("%d ", *p); // 7
*p = *p + 3;
printf("%d ", *p); // 10
Т.е. “a” и “p” отныне синонимы, и где можно использовать одно, можно использовать и другое, в т.ч. в математических операциях, операторах и функциях ввода-вывода, и т.д. Но указатель можно и “перенастроить” на другую переменную:
int b = 21;
p = &b;
printf("%d ", *p); // 2
Значение указателя определяет начало куска памяти, с которым идёт работа (левую границу ячейки памяти), а тип, который стоит слева от звёздочки - размер этого куска (а следовательно, и правую границу ячейки памяти). Пусть далее все указатели имеют значение 0x402000 (16-чное число), а по этому адресу хранятся данные:
0x402000: F7 E6 D5 C4 B3 A2 91 80
Обратите внимание, что в архитектурах little-endian, к коим относится Intel’овская x86, байты в памяти располагаются в обратном порядке.
int *pi;
long long *pl;
char *pc;
Пусть все эти указатели указывают на байт со значением F7. Тогда pi будет равно 0xC4D5E6F7
, *pl == 0x8091A2B3C4D5E6F7
, *pc == 0xF7
.
Для указателей, кроме взятия адреса &
и разыменования *
, определены следующие операции:
(1) Сложение указателя с целым числом.
int *q = pi + 1;
q
будет указывать на следующую за pi
ячейку памяти, а поскольку она размером 4 байта (размер int’а), то численно q будет равняться 0x4020004
, а q будет равно 0x8091A2B3
.
q++; // теперь q будет равняться 0x4020008.
pl + 2
будет равняться 0x402010
. А pc + 3 == 0x402003
. И вообще, при сложении указателя type *p;
с целым числом k
выражение p + k
численно равно p + k * sizeof(type)
.
(2) Разность указателя и целого числа. Ну, думаю, тут всё понятно. int
знаковый тип.
int *z = pi - 1; // будет численно равно 0x401FFC.
(3) Разность двух указателей.
int d = q - z; // равна 3
- На выходе имеем тип целое число, численно равное
(p2-p1)/sizeoftype) = (q-z)/sizeof(int)
. Т.е. разность измеряется в количестве ячеек памяти определённого размера. Обратите внимание: если вычитаем указатели наlong
или наchar
или на ещё что-нибудь, то всё равно получаемint
. Думаю, понятно, зачем это ввели: для симметрии со сложением указателя с числом, для полноты адресной арифметики, чтобы не было сюрпризов в сложных выражениях.
И ещё: другие операции для указателей не определены. Мы не имеем права сравнивать указатели так:
if (p1 > p2) // если ваш компилятор такое позволяет, ждите беды (UB)
но зато вот так OK:
if (p1 - p2 > 0)
Складывать указатель с указателем, понятное дело, тоже нельзя, т.к. на выходе получаем бред.