The Stack vs the Heap
Every C++ program has access to two fundamentally different regions of memory: the stack and the heap. Understanding the difference between them is essential because it explains how variables come into existence, how long they live, what happens when a function returns, and why some memory bugs (like dangling pointers) occur. The key difference is lifetime and control: stack memory is managed automatically by the language, while heap memory is managed explicitly by the programmer (or, in modern C++, by smart pointer wrappers).
The stack
The stack is a region of memory managed in a strict last-in, first-out (LIFO) order — like a stack of plates. Every time a function is called, a stack frameis pushed onto the stack. The frame holds the function's local variables, its parameters, and the return address (where to resume after the function finishes). When the function returns, its frame is popped off and all of its local variables instantly cease to exist.
All local variables — the ones you declare inside a function without new — are stack-allocated. Stack allocation is extremely fast (just moving a stack pointer register) and deallocation is free (just moving it back). The limitation is that the stack is finite — typically 1–8 MB on modern systems — and allocations happen at compile time (the compiler must know the size at compile time).
void inner()
{
int local = 42; // allocated on inner's stack frame
std::cout << local; // fine — local exists here
} // local is destroyed here — frame popped
void outer()
{
int x = 10; // on outer's frame
inner(); // inner's frame is pushed, runs, then popped
// inner's 'local' is gone; x still exists
} // x is destroyed here
// Stack variables are gone when their scope ends:
{
int temp = 99;
}
// temp is gone — the block is its scopeThe heap
The heap (also called the free store) is a much larger pool of memory managed dynamically. You request heap memory at run-time using new, which returns a pointer to the allocated block. That memory stays alive until you explicitly release it with delete, regardless of which function allocated it or whether that function has returned. This flexibility comes with responsibility: if you forget to delete, the memory is leaked; if you delete and then continue using the pointer, you have a use-after-free bug.
int* allocate()
{
int* p = new int(42); // allocate an int on the heap, return its address
return p; // function returns — but heap memory persists!
}
int main()
{
int* ptr = allocate(); // ptr holds the address of the heap int
std::cout << *ptr; // 42 — still valid
delete ptr; // releases the heap memory
ptr = nullptr; // good practice: null the pointer after delete
}
// Compare: this is WRONG — stack address escaping:
int* wrong()
{
int local = 42;
return &local; // DANGER: local is destroyed when function returns
} // the returned pointer is dangling — UB to dereferenceStack vs heap — comparison
| Property | Stack | Heap |
|---|---|---|
| Allocation | Automatic (function call / variable declaration) | Manual (new / make_unique) |
| Deallocation | Automatic (end of scope) | Manual (delete) or smart pointer |
| Speed | Very fast (pointer arithmetic) | Slower (allocator must find free block) |
| Size limit | Small (1–8 MB typical) | Large (limited by virtual memory) |
| Size known at? | Compile time | Run time |
| Lifetime | Tied to enclosing scope | Controlled by programmer |
| Risk | Stack overflow if too deep / large | Memory leaks, dangling pointers, double-free |
When to use the stack, when the heap
The default should always be the stack. Most local variables — integers, strings, vectors, even structs — should be declared as local variables without new. Note that std::vector and std::string are stack objects that internally allocate their buffer on the heap — you get heap flexibility with stack lifetime management. Use the heap (via smart pointers) when:
// STACK — prefer this by default:
std::vector<int> data = {1, 2, 3}; // data is on the stack;
// its internal buffer is on the heap,
// but vector manages it for you
std::string name = "Alice"; // same: stack object, heap buffer inside
// HEAP via smart pointer — use when lifetime must be controlled:
#include <memory>
auto widget = std::make_unique<Widget>(100, 200); // heap, managed by unique_ptr
// no delete needed — unique_ptr destroys Widget when it goes out of scope
// HEAP raw — avoid in modern C++:
Widget* w = new Widget(100, 200); // error-prone
delete w; // must not forget thisKey rules to remember
Local variables live on the stack — they are destroyed when their scope ends
Never return a pointer or reference to a local variable. The variable is gone when the function returns, leaving a dangling pointer.
Heap memory lives until explicitly freed — or until the smart pointer goes out of scope
Forgetting delete causes a memory leak. Using memory after delete causes a use-after-free. Smart pointers (unique_ptr, shared_ptr) eliminate both problems.
std::vector and std::string are stack objects with internal heap buffers
You don't new a vector — you just declare it locally, and it manages its own heap buffer through RAII.
Default to the stack; use the heap only when you have a specific reason
Stack allocation is simpler, faster, and automatically managed. Heap allocation adds complexity and risk.