Что такое указатель и адрес
Урок вводит главное понятие C — указатель: что такое адрес переменной, как взять его оператором &, как объявить указатель и прочитать значение по адресу через разыменование *.
Указатель — это переменная, которая хранит не значение, а адрес другого значения в памяти. Освоить указатели — значит по-настоящему понять C.
Каждая переменная живёт по какому-то адресу в памяти — это просто номер ячейки. Оператор & («взять адрес») возвращает адрес переменной. Указатель — это переменная, которая хранит такой адрес. Объявляется со звёздочкой:
int x = 42;
int *p = &x; // p хранит АДРЕС переменной x
printf("Значение x: %d\n", x); // 42
printf("Адрес x: %p\n", (void*)&x); // напр. 0x7ffe...
printf("p хранит: %p\n", (void*)p); // тот же адрес
printf("По адресу p лежит: %d\n", *p); // 42 — разыменование
Здесь работают два оператора-«антипода». &x берёт адрес переменной. А *p делает обратное — «иди по этому адресу и возьми значение». Это называется разыменование. Через *p можно не только читать, но и писать: *p = 100; изменит саму x.
Как работает под капотом
Представьте память как ряд пронумерованных ячеек. Переменная x лежит в одной из них, а указатель p — в другой, и хранит номер ячейки x:
Адрес Содержимое Имя
-------- ------------ ----
0x1000 [ 42 ] x <-- значение
0x1008 [ 0x1000 ] p <-- p хранит АДРЕС x
&x = 0x1000 (адрес x)
p = 0x1000 (p указывает на x)
*p = 42 (значение по адресу, что хранит p)
Графически: p ----> x
[0x1000] [42]
Стрелка p -> x читается «p указывает на x». Когда вы пишете *p, вы идёте по стрелке и попадаете в x. Когда пишете *p = 100, вы изменяете x, не упоминая её по имени. Именно так функции из прошлого раздела меняли переменные вызывающего.
Частые ошибки
- Путаница объявления и разыменования. В
int *pзвёздочка означает «p — указатель». В*p = 5та же звёздочка — «запиши по адресу». Контекст разный. - Неинициализированный указатель.
int *p;без присваивания указывает «в никуда»; разыменование такого указателя — крах. - Разыменование NULL. Указатель со значением
NULLнамеренно «ничей»;*pприp == NULLвызывает падение программы. - Печать указателя как
%d. Адрес выводят через%p, а не%d.
Best practices
- Инициализируйте указатели сразу — адресом реальной переменной или
NULL, если адреса пока нет. - Перед разыменованием проверяйте указатель на
NULL:if (p != NULL) { ... *p ... }. - Читайте объявления справа налево:
int *p— «p есть указатель на int». Это помогает в сложных типах.
В Python нет явных указателей, но идею «отдельная таблица: имя -> адрес -> значение» можно смоделировать словарём-памятью. Так становится виден механизм разыменования.
# Моделируем память как словарь адрес -> значение
memory = {0x1000: 42} # по адресу 0x1000 лежит значение 42
x_addr = 0x1000 # "адрес x" (как &x в C)
p = x_addr # указатель p хранит адрес
print("p хранит адрес:", hex(p))
print("*p (значение по адресу):", memory[p]) # разыменование
memory[p] = 100 # *p = 100 — пишем по адресу
print("После *p = 100, значение x:", memory[x_addr]) # 100
Та же логика на Python ▶ — словарь играет роль памяти, ключ — адрес, доступ memory[p] — это разыменование *p. В C всё то же, только адреса настоящие.
Типизация указателей
У указателя есть тип, и это не формальность. int * и char * — разные типы, хотя оба хранят адрес. Тип говорит компилятору две вещи: сколько байтов читать при разыменовании и на сколько сдвигаться при арифметике (об этом — отдельный урок). Поэтому нельзя бездумно присвоить указатель одного типа другому. Есть особый «универсальный» указатель void * — он хранит адрес, но не помнит тип, поэтому разыменовать его напрямую нельзя, нужно сначала привести к конкретному типу. Именно void * возвращает malloc, ведь функция не знает, подо что вы выделяете память. А ещё указатели бывают на указатели (int **): такая «двойная стрелка» нужна, когда функция должна изменить сам указатель вызывающего, а не только то, на что он смотрит.
Итоги
Указатель — переменная, хранящая адрес другого значения. Оператор & берёт адрес, оператор * разыменовывает (читает или пишет значение по адресу). Объявление int *p и операция *p используют одну звёздочку в разных ролях. Главные опасности — неинициализированный указатель и разыменование NULL; защита — инициализация и проверка перед использованием.