Dataslope logoDataslope

The Memory Model

Stack, heap, globals, and code — where your data lives, who manages it, and how long it stays alive.

We've already met the stack in detail. Let's now zoom out and look at every place your data can live, the lifetime rules for each, and the trade-offs between them. By the end of this chapter you should be able to look at any variable in C++ and answer three questions:

  1. Where does it live? (stack, heap, globals, code)
  2. When does it die? (end of scope, manually with delete, end of program, never)
  3. Who is responsible for it? (the compiler, you, or the OS)

The four regions, one by one

1. Text (code) segment

The compiled machine code of your program. Read-only — the OS marks those pages so that trying to write to them crashes the program. You don't directly interact with this region.

2. Data segment (globals and statics)

Variables declared outside any function, and variables declared static inside a function, live here.

  • They are zero-initialized by default (unlike locals!).
  • Their lifetime is the entire program: they're alive before main runs and after it returns.
  • They have a fixed address; pointers to them never dangle.
Code Block
C++ 20 (202002L)

Notice call_count keeps incrementing — it lives in the data segment, not in the function's stack frame. A static local is a hidden global with restricted visibility.

Use globals sparingly. They make code hard to test and reason about because any function can change them. Prefer parameters and returns.

3. Stack

Local variables and function parameters. Frames pushed on call, popped on return. Fast to allocate (just bump a pointer), limited in size, automatic in cleanup.

void foo() {
    int x = 42;       // stack
    double pi = 3.14; // stack
    char buf[64];     // stack -- 64 bytes
}                      // x, pi, buf all gone here

4. Heap

The big, dynamic playground. Memory you ask for at runtime with new (or in C, malloc). It stays alive until you release it with delete (or free).

int* p = new int(42);   // heap allocation, p points to it
// ... use *p ...
delete p;               // free it; forgetting is a memory leak

Compared to the stack:

AspectStackHeap
Allocationbump a pointer (cycles)system bookkeeping (~100 cycles)
Deallocationframe pop (free)another bookkeeping call
Size limitsmall (a few MB)huge (gigabytes)
Lifetimetied to scopeyou decide
Concurrencyper-threadshared, needs care
Failure modeoverflow → crashleak / corruption / use-after-free

A common mental model: stack is for things whose lifetime matches a function call. Heap is for everything else.

A diagram of stack vs heap interaction

When you allocate on the heap, the pointer (typically 8 bytes on 64-bit systems) lives on the stack. The object lives on the heap.

When the function returns, the stack pointer p is destroyed. But the heap object stays alive forever — unless something explicitly calls delete. That's a memory leak. We'll spend a whole later chapter on smart pointers that prevent this automatically.

Lifetime in pictures

Globals span the whole program. Stack locals span only the function call. Heap objects span between new and delete — which can be anywhere.

Storage duration vs scope

These two ideas often get mixed up:

  • Scope = the region of source code where the name is visible.
  • Storage duration = how long the memory is alive at runtime.

For a normal local variable they overlap perfectly. For a static local, the scope is small (only inside the function) but the storage duration is global. For a heap object, the storage duration is "until you free it" — but if you lose the pointer to it before freeing it, you've leaked it.

Why C++ exposes all this

In Python or Java, you rarely think about where an object lives. Garbage collection takes care of it. That convenience has a price: unpredictable pauses, larger memory footprint, less predictable performance.

C++ takes the opposite trade-off. The language gives you tools (stack, heap, statics, smart pointers, RAII) and trusts you to choose the right one. Once you understand the model, the choices are usually obvious:

  • Tiny, scoped-to-this-function data? Stack.
  • Data that survives across function calls but is bounded in count? Globals (sparingly) or a class member.
  • Data whose size or count you don't know at compile time? Heap (via std::vector, std::string, smart pointers).
  • Big, owned by exactly one place? Heap, via std::unique_ptr.

We'll meet std::unique_ptr and friends in a couple chapters.

What std::vector and std::string really do

std::vector<int> is the canonical "modern C++" container. Where does its data live?

The vector handle lives on the stack — three small fields: a pointer to the data, the current size, and the current capacity. The actual elements live on the heap. When the vector goes out of scope, its destructor automatically frees the heap data. You never call delete on a vector's contents. That is RAII in action.

Code Block
C++ 20 (202002L)

Watch the capacity grow in geometric jumps (typically doubling). That's the vector reallocating a bigger heap block when it runs out of room. The amortized cost of push_back is therefore constant.

A worked memory diagram

Trace this program in your head before running it:

Code Block
C++ 20 (202002L)
VariableLives inFrees how
globals_live_heredata segmentend of program
on_the_stackstack frame of mainwhen main returns
handle_on_stackstack frame of maindestructor at end of scope
The ints inside the vectorheapthe vector's destructor
heap_int (the pointer)stack frame of mainwhen main returns
*heap_int (the int)heaponly when delete heap_int; runs

The right column is what really matters. Stack things get freed for free. Heap things need either you (raw new/delete) or a smart container/pointer to do it for you.

Test your understanding

QuestionSelect one

Where does a static local variable inside a function live?

On the stack, in the function's frame.

On the heap, allocated by new each call.

In the data segment, alongside globals — its memory survives between calls.

In CPU registers.

QuestionSelect one

When a function with a std::vector<int> local variable returns, what happens to the vector's elements on the heap?

They leak unless the user called delete first.

The OS reclaims them at process exit.

The vector's destructor runs automatically when the local goes out of scope, and it frees the heap memory for you.

The compiler issues a warning.

QuestionSelect one

Which of these allocations is cheapest at runtime?

Hint: anything involving new or a heap buffer pays allocator bookkeeping; a plain local does not.

new std::vector<int>(1000)

int x = 0; inside a function.

int* p = new int[1000];

A std::string of length 1000 declared as a local.

QuestionSelect one

What is the difference between scope and storage duration?

They are two names for the same thing.

Scope is runtime, storage duration is compile-time.

Scope is the region of source code where a name is visible; storage duration is how long the underlying memory lives at runtime.

Storage duration only applies to heap allocations.

Next: pointers and references — the language tools that let you name memory.

On this page