Stack and heap; new and delete
What you'll learn:
- tell objects on the stack from objects on the heap;
- create and delete objects with
newanddelete; - hand deletion to an owner:
std::unique_ptrorstd::shared_ptr; - spot a leak, a double delete and the use of a deleted object.

Picture a canteen. The trays there are kept in a stack: each function call puts its own tray on top, and its local variables stand on it. When the function ends, the top tray is cleared away with everything that stood on it.
Next to the canteen there is a left-luggage room. Nobody clears away what you leave there. You get a ticket with the locker number, and the item stays in the locker until you hand the ticket back. Lose the ticket, and the locker stays taken.
The stack of trays is the stack, the left-luggage room is the heap, and the ticket is a pointer holding an address. Where the analogy stops: a cloakroom attendant checks that a ticket has not been handed back already, and C++ does not.
A local variable is destroyed at the end of its block, so a pointer to it returned from a function dangles. An object created by a new expression lives longer: until the program deletes it with a delete expression.
#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} creates an int with the value start and returns its address. The pointer counter is destroyed when make_counter returns, together with its tray. The number stays in its locker, and main gets the ticket: its address. When the number is no longer needed, delete c deletes it.
Stack and heap
Local variables live on the stack, our stack of trays. Space on it is taken when a block starts and released when the block ends, without a single line of code. Objects from new live in another area of memory, the heap, our left-luggage room. An object on the heap lives until delete is called with its address, however many blocks and functions end before that.
Nothing deletes a heap object automatically. If its address is lost and delete never runs, the memory stays taken until the program ends. This is a memory leak: a long-running program that loses objects takes up more and more memory.
Arrays on the heap
new int[n] creates n numbers in a row and returns the address of the first one. A C array's size must be known at compile time, while n may be known only once the program runs. Such an array is deleted with delete[], with square brackets:
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;An array from new[] must not be deleted with plain delete, and a single object must not be deleted with delete[]. Such a mix-up is undefined behavior, and the compiler warns about it only in simple cases.
Deleting through a base class
An object of a derived class can be created with new and its address kept in a pointer to the base class. The object may then be deleted through that pointer only if the base class destructor is virtual. Such a delete runs the derived class destructor first, then the base one:
#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
}Without virtual on ~Base, such a delete is undefined behavior: the standard does not even guarantee that ~Derived runs.
Common mistake
An object is used after delete:
int main() {
int* p = new int{7};
delete p;
*p = 8; // ✗
}The program builds, but the object at the address in p no longer exists, so the write *p = 8 is undefined behavior. The same holds for any copy of that address. After int* q = p; and delete p, the pointer q also leads to a deleted object. If the pointer is still needed, set it to nullptr right after delete: then p != nullptr shows whether an object is there. Copies of the address are not affected: q still holds the old address, and q != nullptr is true.
Key points
- Local variables live on the stack and vanish at the end of their block; objects from
newlive on the heap untildelete. - An array from
new[]is deleted withdelete[], a single object withdelete. - Deleting through a pointer to a base class needs a virtual destructor.
- After
delete, the object must not be touched throughpor through any copy of its address.
Subscribe to take the course
The course is free. Sign in with Telegram and subscribe to the channel to open every lesson.