Skip to content
C++

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 scope

The 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 dereference

Stack vs heap — comparison

PropertyStackHeap
AllocationAutomatic (function call / variable declaration)Manual (new / make_unique)
DeallocationAutomatic (end of scope)Manual (delete) or smart pointer
SpeedVery fast (pointer arithmetic)Slower (allocator must find free block)
Size limitSmall (1–8 MB typical)Large (limited by virtual memory)
Size known at?Compile timeRun time
LifetimeTied to enclosing scopeControlled by programmer
RiskStack overflow if too deep / largeMemory 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:

→The object must outlive the function that creates it (e.g., objects stored in a container across multiple function calls)
→The size is not known at compile time (e.g., reading a file whose size is only known at run-time)
→The object is very large — putting a large array on the stack risks a stack overflow
→You need to share ownership of an object between multiple parts of the program (std::shared_ptr)
// 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 this

Key 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.

Sign in to track progress