Stack vs Heap Memory Allocation
Pointers, References and Memory Layout

3.3 Stack vs Heap Memory Allocation

I will explain where your variables live in physical memory. The choice between Stack and Heap defines both the execution speed and the lifecycle of your C++ applications. Managing this partition is what makes systems programmers different from application programmers.

Let's look at the differences, allocations, and systems hazards of these two memory zones.

The Stack: Automatic and Ultra Fast

The stack is a contiguous region of memory managed automatically by the CPU's stack pointer. When a function is called, its local variables are pushed onto the stack. When the function returns, the stack pointer moves back, and all those variables are instantly discarded.

* Cafeteria Tray Metaphor: Stack memory is like a stack of cafeteria trays. You add trays to the top and remove them from the top (Last In, First Out). It is incredibly fast because allocating memory is just shifting a single pointer register, but you cannot request a dynamic amount of space at runtime.

The Heap: Large and Dynamic

The heap is a massive pool of memory managed by the operating system. You use the heap when you do not know how much memory you need at compile time (such as loading a file or a user variable size) or when you want variables to survive after a function exits.

To allocate memory on the heap, we use the new operator. To return it to the OS, we must use the delete operator:

int main() {
    // Allocate a single integer on the heap
    int* heapPtr{new int{42}};

    std::cout << "Value on heap: " << *heapPtr << '\n';

    // Free the memory! If you forget this, you get a memory leak.
    delete heapPtr;

    // Prevent dangling pointer by zeroing the address
    heapPtr = nullptr;
    return 0;
}
Allocating and deleting heap memory.

Memory Leaks and Dangling Pointers

Because C++ has no garbage collector, you are manually responsible for every byte of heap memory you request. This leads to two legendary systems bugs:

1. Memory Leaks

If you allocate memory on the heap and lose the pointer storing its address without calling delete, that memory remains reserved. If this happens inside a loop, your program will slowly consume all your RAM until the operating system terminates it.

* The Storage Locker Metaphor: Heap memory is like renting a public storage locker. You can store whatever you want inside, but if you lose the key (lose the pointer), you cannot open the locker to empty it, yet you keep paying rent forever (a memory leak) until your credit card declines.

2. Dangling Pointers

If you call delete on a pointer, the memory is returned to the OS. However, the pointer variable still stores that old memory address! If you try to dereference or use that pointer again, you will corrupt memory or crash the program. Always set pointers to nullptr after deleting them.

int* danger{new int{100}};
delete danger; // Memory is freed

// danger still points to the old address!
// *danger = 50; // DANGEROUS: Accessing freed memory triggers Undefined Behavior.
danger = nullptr; // Safe: Pointer is now zeroed.
Dangling pointer hazard demonstration.

Stack Overflow: Physics of the Stack

Because stack frames are stacked LIFO, the compiler must know the sizes of variables. The stack has a small, restricted size allocated by the operating system (typically between 1MB and 8MB).

If you attempt to allocate a massive static variable or trigger infinite function recursion, you run out of stack space entirely. The stack pointer pushes past the boundary line, resulting in a stack overflow crash:

void recursiveCrash() {
    int giantBuffer[100000]{}; // Consumes 400KB of stack space on each recursion!
    recursiveCrash();          // Crashing the stack after just a few calls
}
Exceeding stack bounds with recursive overflows.

Allocation Latency Benchmarks

Why not use the heap for everything? Because the stack is physically much faster. Allocating stack space is done in a fraction of a nanosecond with a single CPU instruction:

sub rsp, 16 ; Shifting the stack pointer down is instant

Heap allocation, however, requires a system call to the operating system memory allocator. The allocator must traverse lists of free blocks, locate an empty pocket of space, deal with thread locks, and map the memory, taking hundreds of nanoseconds. Always prioritize the stack for performance critical functions.

Smart Pointers (Safe Dynamic Memory)

Because manually calling new and delete is error prone, modern C++ introduces Smart Pointers (defined in the <memory> header). Smart pointers are class wrappers around raw heap pointers that automatically delete the managed memory when they exit scope using RAII.

The most common smart pointer is std::unique_ptr, which holds exclusive ownership of a heap resource:

#include <memory>
#include <iostream>

struct Asset {
    Asset() { std::cout << "Asset acquired!\n"; }
    ~Asset() { std::cout << "Asset destroyed and freed!\n"; }
};

int main() {
    {
        // Allocate heap memory safely using make_unique
        std::unique_ptr<Asset> ptr{std::make_unique<Asset>()};
        // No delete call needed! Memory is automatically freed when ptr exits scope.
    }
    return 0;
}
Safe heap allocations using std::unique_ptr.
Finished reading this lesson?