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:
- Where does it live? (stack, heap, globals, code)
- When does it die? (end of scope, manually with
delete, end of program, never) - 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
mainruns and after it returns. - They have a fixed address; pointers to them never dangle.
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 here4. 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 leakCompared to the stack:
| Aspect | Stack | Heap |
|---|---|---|
| Allocation | bump a pointer (cycles) | system bookkeeping (~100 cycles) |
| Deallocation | frame pop (free) | another bookkeeping call |
| Size limit | small (a few MB) | huge (gigabytes) |
| Lifetime | tied to scope | you decide |
| Concurrency | per-thread | shared, needs care |
| Failure mode | overflow → crash | leak / 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.
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:
| Variable | Lives in | Frees how |
|---|---|---|
globals_live_here | data segment | end of program |
on_the_stack | stack frame of main | when main returns |
handle_on_stack | stack frame of main | destructor at end of scope |
The ints inside the vector | heap | the vector's destructor |
heap_int (the pointer) | stack frame of main | when main returns |
*heap_int (the int) | heap | only 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
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.
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.
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.
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.