Стек и куча; new и delete
Чему вы научитесь:
- отличать объекты в стеке от объектов в куче;
- создавать и удалять объекты через
newиdelete; - поручать удаление владельцу:
std::unique_ptrилиstd::shared_ptr; - находить утечку, двойное удаление и обращение к удалённому объекту.

Представьте столовую. Подносы там складывают стопкой: каждый вызов функции кладёт сверху свой поднос, и на нём стоят её локальные переменные. Когда функция заканчивается, верхний поднос убирают вместе со всем, что на нём стояло.
Рядом со столовой есть камера хранения. Вещь, сданную туда, никто не уберёт сам. Вы получаете номерок с номером ячейки, и вещь лежит в ячейке, пока вы не вернёте номерок. Потеряли номерок — ячейка так и остаётся занятой.
Стопка подносов — это стек, камера хранения — куча, а номерок — указатель с адресом. Где аналогия заканчивается: гардеробщик проверит, не сдан ли уже номерок, а C++ этого не делает.
Локальная переменная уничтожается в конце своего блока, поэтому указатель на неё, возвращённый из функции, будет висячим. Объект, созданный выражением new, живёт дольше: пока программа не удалит его выражением delete.
#include <iostream>
int* make_counter(int start) {
int* counter = new int{start};
return counter;
}
int main() {
int* c = make_counter(5);
*c = *c + 1;
std::cout << *c << '\n'; // 6
delete c;
}new int{start} создаёт число типа int со значением start и возвращает его адрес. Указатель counter уничтожается при выходе из make_counter вместе с её подносом. Число остаётся в ячейке, и main получает номерок — его адрес. Когда число больше не нужно, delete c его удаляет.
Стек и куча
Локальные переменные хранятся в стеке — это наша стопка подносов. Место в нём занимается при входе в блок и освобождается при выходе, без единой строки кода. Объекты из new лежат в другой области памяти — в куче, нашей камере хранения. Объект в куче живёт до вызова delete с его адресом, сколько бы блоков и функций ни завершилось.
Сам по себе объект в куче не удаляется. Если адрес потерян, а delete не выполнен, память остаётся занятой до конца программы. Это утечка памяти: программа, которая долго работает и теряет объекты, занимает всё больше памяти.
Массивы в куче
new int[n] создаёт n чисел подряд и возвращает адрес первого. У C-массива размер должен быть известен уже при компиляции, а n можно узнать и во время работы программы. Такой массив удаляют через delete[], с квадратными скобками:
int n = 4;
int* squares = new int[n];
for (int i = 0; i < n; ++i) {
squares[i] = i * i;
}
std::cout << squares[n - 1] << '\n'; // 9
delete[] squares;Массив из new[] нельзя удалять обычным delete, а одиночный объект — через delete[]. Такая путаница — неопределённое поведение, и компилятор предупреждает о ней только в простых случаях.
Удаление через базовый класс
Объект производного класса можно создать через new и хранить его адрес в указателе на базовый класс. Тогда удалять объект через этот указатель можно, только если деструктор базового класса виртуальный. Такой delete вызывает сначала деструктор производного класса, затем базового:
#include <iostream>
struct Base {
virtual ~Base() { std::cout << "~Base\n"; }
};
struct Derived : public Base {
~Derived() { std::cout << "~Derived "; }
};
int main() {
Base* p = new Derived{};
delete p; // ~Derived ~Base
}Без virtual у ~Base такой delete — неопределённое поведение: стандарт не гарантирует даже вызова ~Derived.
Частая ошибка
Объект используют после delete:
int main() {
int* p = new int{7};
delete p;
*p = 8; // ✗
}Программа собирается, но объекта по адресу из p уже нет, и запись *p = 8 — неопределённое поведение. То же касается любой копии этого адреса: после int* q = p; и delete p указатель q тоже ведёт к удалённому объекту. Если указатель ещё нужен, присвойте ему nullptr сразу после delete. Тогда проверка p != nullptr покажет, есть ли за ним объект. Копии адреса это не касается: q по-прежнему хранит старый адрес, и q != nullptr верно.
Главное
- Локальные переменные живут в стеке и исчезают в конце блока, объекты из
newживут в куче доdelete. - Массив из
new[]удаляют черезdelete[], одиночный объект — черезdelete. - Удаление через указатель на базовый класс требует виртуального деструктора.
- После
deleteобъект нельзя трогать ни черезp, ни через любую копию адреса.
Подпишись, чтобы пройти курс
Курс бесплатный. Войди через Telegram и подпишись на канал — откроются все уроки.