TRT
Sign in
AlgorithmsUpdated

Stack vs Heap: Where Memory Lives

Understand the difference between stack memory (fast, automatic) and heap memory (dynamic allocation), and when to use each.

The Runtime Theory Team4 min read

A running program keeps its data in regions with different rules. Two of them matter most to beginners.

The stack holds function locals with automatic cleanup. The heap holds objects with lifetimes you control. Knowing which region a value belongs in is most of what "knowing memory" means.

This article compares their speed, limits, and lifetimes. It ends with rules for choosing between them.

The stack allocates with one instruction

Each thread owns a stack that grows as functions are called. Creating a local variable moves the stack pointer down. Leaving the function moves it back up. In assembly that looks like a single sub rsp, 400 for 400 bytes of arrays.

There is no allocator involved. No free list gets searched. No lock gets taken. Allocation and release each cost about one cycle. Consecutive locals sit side by side, so they share cache lines.

The same fixed buffer in three languages:

#include <iostream>
// Sum 100 numbers held in a function-local array.
// The array lives in this frame and vanishes on return.
int demo() {
    // 400 bytes claimed with one stack-pointer move.
    int arr[100];
    for (int i = 0; i < 100; i++) arr[i] = i + 1; // fill 1..100
    int total = 0;
    for (int i = 0; i < 100; i++) total += arr[i]; // sequential sweep
    return total; // 5050; the array itself is gone by now
}

The C++ array truly sits in the stack frame. The Java and Python versions allocate heap objects and keep only the handle locally. The comments mark that difference because it surprises beginners: only C++ of the three gives you stack arrays directly.

Stack speed comes with three constraints. Sizes must be known when the frame is entered. The total per thread is capped, commonly at 8 MB on Linux. Lifetime ends at the closing brace: returning a pointer to a local hands out an address the next call overwrites.

The heap outlives the call at a bookkeeping cost

The heap is a shared pool for objects with arbitrary size and lifetime. Allocators maintain free lists per size class, per-thread caches that avoid locking, and merging logic that fights fragmentation.

That service costs time. A typical allocation takes 20 to 50 nanoseconds: hundreds of times slower than a stack-pointer move, still trivial in isolation. Problems start only when hot loops allocate millions of times.

The same heap handoff in three languages:

#include <memory>
// Build a result on the heap and hand it to the caller.
// Ownership transfers with the return value: no leak, no copy.
std::unique_ptr<int> demo() {
    // One heap integer; the unique_ptr owns it from birth.
    auto p = std::make_unique<int>(5050);
    return p; // ownership moves out; nothing is freed here
} // the caller now owns the integer and frees it by scope exit

All three objects survive the return. The C++ version transfers ownership explicitly through a smart pointer. The Java and Python versions rely on garbage collection. The outcome matches: heap lifetime follows need, not scope.

Limits and failure modes follow the region rules

Each region fails in its own way. The failure names the rule that was broken.

Deep recursion overflows the stack. A frame of 1 KB exhausts 8 MB at roughly 8,000 levels. The operating system reports a segmentation fault because the stack touched its guard page. Big local buffers cause the same crash through one frame instead of many.

Returning a local's address creates a dangling pointer. The frame memory gets reused by the next call. Reads return plausible garbage rather than crashing, which lets the bug hide through testing.

Forgetting to release heap memory leaks it. The bytes stay reserved until the program ends. Long-running servers bloat over days from leaks of a few bytes per request. Fragmentation adds a slower variant: free memory exists but no single run fits the request.

text
High address
  stack       <- grows down: frames and locals, freed automatically
  ...
  heap        <- grows up: new, malloc, vector buffers, shared objects
  data        <- globals and statics, alive for the whole run
  text        <- compiled code, read-only
Low address

Address-space randomization scatters these regions in real processes. The diagram shows the logical order, not exact addresses.

Choose by size, lifetime, and sharing

Three questions pick the region. How big is the value. How long must it live. Who else needs it.

Small, short-lived, single-owner values belong on the stack. Scalars, small arrays, and handle objects fit here. Allocating 16 bytes on the heap for a value used inside one function is pure overhead.

Values that outlive the frame belong on the heap. So do values sized at runtime beyond a safe frame budget, and values shared across threads or callbacks.

Ownership should travel in wrappers, not raw calls. Containers hold heap buffers behind stack-lifetime objects. Smart pointers pair heap storage with automatic cleanup. Modern C++ almost never calls bare new: make_unique and make_shared create the object already owned.

AspectStackHeap
Allocation costabout one instructionabout 20 to 50 ns plus bookkeeping
Size limitabout 8 MB per threadavailable RAM
Size knownat frame entryat runtime
Lifetimescope, automaticmanual or wrapper-managed
Layoutcontiguous, cache-friendlypossibly scattered
Failure modeoverflow, dangling localsleaks, fragmentation

The split explains everyday observations. Recursion depth limits come from the stack cap. Vector growth costs come from heap reallocation. Server memory graphs that climb forever point at heap leaks.

It also explains language design. Java and Python allocate objects on the heap and manage them with collectors. C++ exposes both regions and trusts you to choose, with wrappers that make the heap behave.

Continue with what a pointer is for the addresses that connect the regions. Read RAII and smart pointers for ownership that makes heap use safe. The memory layout diagram draws the full process picture.

Spotted a mistake or something broken? Report a problem

Not started

Sign in to save your learning progress.

Sign in to save