Указатель - это переменная, содержащая адрес. Адрес другой переменной, адрес массива, структуры или его/её части, адрес выделенного куска памяти, адрес машинной инструкции, библиотеки… просто какой-то левый адрес, даже если по нему нельзя писать или/и читать.

Указатели применяются для косвенного доступа к переменным или другим данным. Иногда обратиться к переменной по её имени не представляется возможным (в т.ч. из-за ограничения области видимости - например, когда она объявлена в другой функции), в этом случае можно попытаться добраться до неё по адресам, с помощью указателей и адресной арифметики. Или переменная/объект вовсе не имеет имени - когда память под него была выделена динамически. Есть и другие случаи применения указателей: например, строки, списки, деревья, графы в Си построены на указателях. Ну и более специфические случаи

Размер переменной-указателя равен размеру машинного слова (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
  1. На выходе имеем тип целое число, численно равное (p2-p1)/sizeoftype) = (q-z)/sizeof(int). Т.е. разность измеряется в количестве ячеек памяти определённого размера. Обратите внимание: если вычитаем указатели на long или на char или на ещё что-нибудь, то всё равно получаем int. Думаю, понятно, зачем это ввели: для симметрии со сложением указателя с числом, для полноты адресной арифметики, чтобы не было сюрпризов в сложных выражениях.

И ещё: другие операции для указателей не определены. Мы не имеем права сравнивать указатели так:

if (p1 > p2) // если ваш компилятор такое позволяет, ждите беды (UB)

но зато вот так OK:

if (p1 - p2 > 0)

Складывать указатель с указателем, понятное дело, тоже нельзя, т.к. на выходе получаем бред.