Stack and heap; new and delete
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, but the number stays, and main gets its address. When the number is no longer needed, delete c deletes it.
Stack and heap
Local variables live on the stack. 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. 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. Unlike a C array, the size n may become known only while 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. A delete through that pointer is then correct 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.