Dynamic Memory (new / delete)
So far, every variable you have created has lived on the stack — memory that is automatically allocated when a variable comes into scope and automatically freed when it goes out of scope. Sometimes you need memory that outlives the function that created it, or whose size isn't known until the program is running. That memory comes from the heap, and in C++ you manage it yourself with new and delete.
Stack vs heap
Aspect | Stack | Heap |
|---|---|---|
Allocation | Automatic, on scope entry | Manual, via new |
Deallocation | Automatic, on scope exit | Manual, via delete |
Speed | Very fast | Slower (bookkeeping overhead) |
Lifetime | Tied to the enclosing scope | Lives until explicitly freed |
Size limits | Small, fixed at compile time (per frame) | Large, limited by available memory |
Risk of leaks | None | Yes, if delete is forgotten |
Allocating with new
The new operator allocates memory on the heap for a single object and returns a pointer to it. That memory stays valid until you explicitly free it — it does not disappear when the function that allocated it returns.
new_basics.cpp
CPP
#include <iostream>
int main() {
int* p = new int; // allocates one uninitialized int on the heap
*p = 42;
std::cout << *p << std::endl;
int* q = new int(100); // allocates and initializes to 100
std::cout << *q << std::endl;
delete p; // free p's memory
delete q; // free q's memory
return 0;
}Every new must have exactly one matching delete
Memory allocated with new is not freed automatically. If you forget to call delete, that memory is never returned to the system for the lifetime of the program — a memory leak (covered in depth next chapter). If you call delete more than once on the same pointer, or delete memory that was never allocated with new, the result is undefined behavior, often a crash. Every successful new needs exactly one delete — no more, no fewer.
new[] and delete[] for arrays
Allocating an array on the heap uses new[], and it must be freed with the matching delete[] — not plain delete.
new_array.cpp
CPP
int size = 5;
int* arr = new int[size]; // heap-allocated array of 5 ints
for (int i = 0; i < size; i++) {
arr[i] = i * i;
}
for (int i = 0; i < size; i++) {
std::cout << arr[i] << " ";
}
std::cout << std::endl;
delete[] arr; // must use delete[], not delete, for array allocationsNever mix delete with new[]
Calling plain delete on memory allocated with new[] (or delete[] on memory allocated with plain new) is undefined behavior. The array form typically stores extra bookkeeping (like the element count) that only delete[] knows how to interpret correctly. Always match new with delete, and new[] with delete[] — never cross them.
Why raw new/delete is now considered bad practice
Manually pairing every new with exactly one delete — across every code path, including early returns and exceptions — is genuinely difficult to get right in any non-trivial program. Forget one delete and you leak memory; call one twice and you corrupt the heap; throw an exception between new and delete and the cleanup never runs at all. This is precisely the class of bug that motivated modern C++'s move toward smart pointers, which wrap a raw pointer in an object that automatically calls delete at the right time — no matter how the enclosing scope is exited. The next few chapters build up to exactly that.
new / delete manage single objects; new[] / delete[] manage arrays — never mix the two forms.
Every heap allocation needs exactly one matching deallocation, on every possible code path.
Modern C++ code should reach for smart pointers (covered soon) instead of calling new/delete directly.
Coming up
The next chapter walks through concrete memory leak and dangling pointer examples — the exact failure modes that raw new/delete makes easy to fall into.