Указатели в C++: что нужно знать заранее
Первое знакомство с указателями обычно заканчивается segmentation fault и часом непонимания. Разбираем, что такое указатель на самом деле, как его объявить, разыменовать и не выстрелить себе в ногу.

Указатель — переменная, которая хранит адрес другой переменной в памяти. Через час вы будете уверенно объявлять их, разыменовывать и понимать, почему программа падает с Segmentation fault. Всё, что нужно на старте, — компилятор с поддержкой C++17 или новее (g++, clang++ или MSVC) и умение компилировать и запускать один файл. Стандарт для примеров ниже — C++17, но всё это работает и в более новых версиях языка.
Что понадобится до начала
- Установленный компилятор. Проверьте командой
g++ --versionилиclang++ --version— должна вывестись версия, а не «команда не найдена». - Понимание, что такое переменная и функция. Про указатели говорим с нуля, про остальное — нет.
- Флаги для отладки: компилируйте с
-Wall -g. Предупреждения компилятора про указатели читайте, они спасают часы.
Адрес и разыменование: две операции, с которых всё начинается
У любой переменной есть значение и есть адрес — место в памяти, где это значение лежит. Оператор & возвращает адрес. Оператор * перед указателем делает обратное — достаёт значение по адресу. Это называется разыменованием.
#include <iostream>
int main() {
int age = 30;
int* ptr = &age; // ptr хранит адрес переменной age
std::cout << "Значение age: " << age << "\n";
std::cout << "Адрес age: " << ptr << "\n";
std::cout << "Значение по адресу: " << *ptr << "\n";
*ptr = 31; // меняем age через указатель
std::cout << "Теперь age: " << age << "\n";
return 0;
}
Скомпилируйте и запустите:
g++ -Wall -g pointers.cpp -o pointers
./pointers
В выводе вы увидите значение 30, потом какой-то адрес вида 0x7ffee... (он свой при каждом запуске), потом снова 30, а в конце 31. Последняя строка — доказательство, что через указатель вы поменяли исходную переменную, не трогая её по имени.
Звёздочка значит разное в разных местах
Одна и та же * в объявлении int* ptr и в выражении *ptr — это две разные вещи. В объявлении звёздочка говорит «это указатель на int». В выражении — «дай значение по адресу». Новичков это путает сильнее всего, поэтому запомните разделение сразу.
Указатель — это не «сложная переменная». Это переменная, которая вместо числа или строки хранит адрес. Как только вы держите это в голове, половина ошибок отпадает сама.
Нулевой указатель и почему программа падает
Указатель может никуда не указывать. Для этого есть nullptr — специальное значение «пусто». Проблема начинается, когда вы разыменовываете такой указатель или указатель, которому вообще ничего не присвоили.
int* ptr = nullptr;
std::cout << *ptr; // падение: разыменование nullptr
Это и есть тот самый Segmentation fault. Операционная система запрещает читать по адресу, который вам не принадлежит, и убивает процесс. Правило простое: перед разыменованием проверяйте, что указатель не nullptr.
if (ptr != nullptr) {
std::cout << *ptr;
} else {
std::cout << "Указатель пуст\n";
}
Ещё опаснее висячий указатель1 — он указывает на память, которая уже освобождена или вышла из области видимости. Такой указатель не равен nullptr, проверка его пропускает, а разыменование даёт неопределённое поведение: иногда падение, иногда молчаливо неверные данные.
Указатель, ссылка и обычная переменная — в чём разница
Новички часто не понимают, зачем указатель, если есть ссылка. Коротко: ссылку нельзя переназначить и нельзя сделать пустой, а указатель — можно.
| Свойство | Обычная переменная | Ссылка (int&) | Указатель (int*) |
|---|---|---|---|
| Хранит | значение | другое имя для переменной | адрес |
| Может быть пустой | нет | нет | да (nullptr) |
| Можно переназначить | да | нет | да |
| Нужно разыменовывать | нет | нет | да (*) |
Практический вывод: если объект точно существует и вы не собираетесь переключать его на другой, берите ссылку — она безопаснее. Указатель нужен там, где значение может отсутствовать или меняться.
new и delete: где новичок теряет память
Оператор new выделяет память в куче и возвращает адрес. Эта память живёт, пока вы её явно не освободите через delete. Забыли delete — получили утечку памяти.
int* data = new int(42); // выделили память под одно число
std::cout << *data << "\n";
delete data; // освободили
data = nullptr; // обнулили, чтобы не остался висячий указатель
int* arr = new int[5]; // массив из пяти int
delete[] arr; // для массива обязателен delete[], а не delete
Два частых промаха: вызвать delete вместо delete[] для массива и вызвать delete дважды на одном адресе. Оба ведут к неопределённому поведению.
Почему в современном C++ new и delete пишут редко
Ручное управление памятью — источник большинства ошибок с указателями. Начиная с C++11 вместо голых указателей на выделенную память используют умные указатели: std::unique_ptr и std::shared_ptr из заголовка <memory>. Они сами вызывают delete, когда объект больше не нужен.
#include <memory>
auto data = std::make_unique<int>(42);
std::cout << *data << "\n";
// delete вызывать не нужно — память освободится сама
На старте важно понимать голые указатели — их полно в чужом коде и учебниках. Но в своём новом коде тянитесь к умным. Устройство unique_ptr и shared_ptr — отдельная большая тема, здесь мы только обозначаем, что они есть.
Если не получилось
Segmentation fault (core dumped)при запуске — вы разыменовалиnullptr, неинициализированный или висячий указатель. Запустите под отладчиком:gdb ./pointers, затемrunи после паденияbacktrace— покажет строку.warning: 'ptr' is used uninitialized— вы объявили указатель, но не присвоили ему адрес. Инициализируйте сразу:int* ptr = nullptr;или конкретным адресом.error: invalid conversion from 'int' to 'int*'— вы присваиваете указателю число вместо адреса. Нужен&:int* ptr = &age;, а неint* ptr = age;.- Программа компилируется, но выдаёт мусорные числа — скорее всего висячий указатель на локальную переменную функции, которая уже завершилась. Не возвращайте адрес локальной переменной из функции.
free(): double free detected— вызвалиdeleteдважды. После первогоdeleteприсвойте указателюnullptr:deleteнаnullptrбезопасен.
Что делать дальше
Соберите любой свой пример с флагом AddressSanitizer: g++ -Wall -g -fsanitize=address file.cpp -o file. Он ловит выход за границы массива, use-after-free и утечки прямо во время запуска и показывает точную строку — на этапе обучения это лучший помощник по указателям. Дальше разбирайтесь с std::unique_ptr и std::shared_ptr: в реальных проектах голыми new/delete почти не пользуются, и понимание владения памятью через умные указатели важнее, чем виртуозность с адресами.
1 Висячий указатель (dangling pointer) — указатель, который хранит адрес уже освобождённой или вышедшей из области видимости памяти. Формально не пустой, но обращаться по нему нельзя.
Универсальный ассистент: Обычный чат: тексты, идеи, разбор, перевод, расчёты.
Спросить за 8 ₽Источники: cppreference — Pointer declaration, cppreference — std::unique_ptr
Частые вопросы
В чём разница между nullptr и NULL?
Обязательно ли инициализировать указатель сразу?
Что будет, если забыть delete?
У меня старый компилятор без C++17, примеры с make_unique не соберутся. Что делать?
Почему программа иногда падает, а иногда работает с тем же кодом?
Когда брать указатель, а когда ссылку?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.