Стек и куча; new и delete
Локальная переменная уничтожается в конце своего блока, поэтому указатель (pointer) на неё, возвращённый из функции, будет висячим (dangling). Объект, созданный выражением 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 его удаляет.
Стек и куча
Локальные переменные хранятся в стеке (stack). Место в нём занимается при входе в блок и освобождается при выходе, без единой строки кода. Объекты из new лежат в другой области памяти — в куче (heap). Объект в куче живёт до вызова delete с его адресом, сколько бы блоков и функций ни завершилось.
Сам по себе объект в куче не удаляется. Если адрес потерян, а delete не выполнен, память остаётся занятой до конца программы. Это утечка памяти (memory leak): программа, которая долго работает и теряет объекты, занимает всё больше памяти.
Массивы в куче
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[]. Такая путаница — неопределённое поведение (undefined behavior), и компилятор (compiler) предупреждает о ней только в простых случаях.
Удаление через базовый класс
Объект производного класса можно создать через new и хранить его адрес в указателе на базовый класс. Тогда delete через этот указатель корректен, только если деструктор (destructor) базового класса виртуальный. Такой 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 покажет, есть ли за ним объект.