Understanding Stack and Heap Memory
Understand how automatic and dynamic storage differ in allocation and lifetime.
What Are the Stack and the Heap?
The stack and the heap are two regions of memory your program uses for entirely different purposes. The call stack holds function parameters and local variables and manages itself as functions are called and return. The heap supplies memory for dynamic allocation, which you request and release yourself.
Where a variable lives decides how fast it is to create, how long it survives, and how much of it you can have. That is why this distinction keeps surfacing when you debug crashes, reason about recursion, or wonder why a variable stopped existing.
The Memory Segments
A running program's memory is divided into segments, each with its own job:
| Segment | Holds | Notes |
|---|---|---|
| Code (or text) | The compiled program itself | Usually read-only |
| bss (uninitialized data) | Zero-initialized globals and statics | |
| Data (initialized data) | Globals and statics with an initial value | |
| Heap | Dynamically allocated objects | You control the lifetime |
| Call stack | Parameters, local variables, call bookkeeping | Managed automatically |
The last two are where the interesting behavior lives.
The Stack Data Structure
A data structure organises data so it can be used efficiently, and you have met several already, such as arrays and structs. A stack is another, and it is deliberately restrictive.
An array gives you random access: read or write any element, in any order. A stack allows only three operations, all at one end:
- Look at the item on top, usually
top(), sometimespeek() - Remove the top item,
pop() - Add a new item on top,
push()
This makes a stack last-in, first-out, or LIFO: the most recently pushed item is the first one available to pop. Pushing grows the stack, popping shrinks it.
| Operation | Contents afterwards |
|---|---|
| start | empty |
| push 10 | 10 |
| push 20 | 10, 20 |
| push 30 | 10, 20, 30 |
| pop | 10, 20 |
| pop | 10 |
How the Call Stack Uses That Shape
The call stack keeps track of every function that has been called but has not yet returned, from main down to whatever is executing right now.
Physically it is not a growing container at all. It is a fixed block of memory addresses, plus one CPU register called the stack pointer that records where the top currently is. Pushing means writing data at the stack pointer and moving the pointer along; popping means moving the pointer back. Everything past the stack pointer is off the stack, whatever bytes happen to remain there.
When the program starts, the operating system pushes main. Every subsequent call pushes a stack frame, and every return pops one, a process also called unwinding the stack. The set of frames currently present is exactly the chain of calls that led to the current line, which is what a debugger shows you as the call stack.
What a Stack Frame Holds
A stack frame carries everything one call needs:
- The return address, the instruction to continue from once the function finishes
- The function's arguments
- Memory for its local variables
- Saved copies of any registers the function will modify and must restore
Calling a function runs this sequence:
- The program reaches the call
- A stack frame is built and pushed
- The CPU jumps to the function's first instruction
- The function body executes
Returning runs it in reverse:
- Saved registers are restored
- The frame is popped, releasing the arguments and locals in one step
- The return value is handed back, via a register or the frame depending on the architecture
- The CPU continues from the return address
For a concrete case:
int transform(int x)
{
return x * 2;
}
int main()
{
transform(15);
return 0;
}
At startup the stack holds only main. During the call it holds main plus a frame for transform containing the return address, the parameter x holding 15, and space for any locals. Once transform returns, that frame is popped and only main remains.
Popping is nearly free because nothing is erased. The stack pointer moves back, and that is all. The old bytes stay where they are until a later frame overwrites them, which is exactly why reading a local variable after its function has returned can appear to work and then fail unpredictably later.
Whether frames sit at higher or lower addresses as the stack grows depends on the architecture, so do not rely on the direction.
Stack Overflow
The stack is a fixed size: 1MB by default on Visual Studio for Windows, and up to 8MB with g++ or Clang on Unix systems. Stack overflow is what happens when a program tries to use more than that, and the allocation spills into memory the program does not own. Modern operating systems respond by terminating the program.
Two things cause it in practice, and they are the same problem from different directions. One is a single frame demanding too much, typically a very large local array: three million int values is around 12MB, which exceeds every default stack size above. The other is too many frames, from deep nesting or from recursion that never reaches its base case.
The following program is broken deliberately, to show the second case:
#include <iostream>
int g_iterations{ 0 };
void recurse()
{
std::cout << ++g_iterations << ' ';
recurse();
std::cout << "done";
}
int main()
{
recurse();
return 0;
}
recurse calls itself with no condition that would stop it, so every call pushes another frame and none are ever popped. The compiler notices:
so3.cpp: In function 'void recurse()':
so3.cpp:5:6: warning: infinite recursion detected [-Winfinite-recursion]
5 | void recurse()
| ^~~~~~~
so3.cpp:9:12: note: recursive call
9 | recurse();
| ~~~~~~~^~
Run anyway, it printed 174,446 iterations in this sandbox before the operating system killed it with a segmentation fault. The number depends on the stack size and how much each frame uses, so expect a different figure on a different machine or build. done is never printed, because no call ever returns.
The Heap
The heap, also called the free store, is where new gets its memory:
int* counter{ new int };
int* collection{ new int[12] };
new finds a suitable block, and the address comes back as a pointer. You do not need to know how the search works, but one property matters: consecutive allocations need not be adjacent in memory. Two new int calls in a row can land anywhere the allocator has room.
delete returns memory to the heap so later requests can reuse it. Deleting a pointer does not destroy the pointer, it releases the memory at the address the pointer holds, and it is on you to do it. Forget, and that memory stays reserved for the life of the program, which is a memory leak.
Choosing Between Them
| Stack | Heap | |
|---|---|---|
| Allocation speed | Fast, just move a pointer | Comparatively slow |
| Lifetime | Ends when the frame pops | Until you delete it, or the program exits |
| Access | Directly through the variable | Through a pointer, so one extra step |
| Size | Small and fixed, roughly 1MB to 8MB | Large, potentially gigabytes |
| Managed by | The program automatically | You |
The practical reading is that the stack is the default and the heap is for the cases the stack cannot serve: objects that must outlive the function creating them, and objects too large to fit comfortably in a frame.
Summary
Memory segments: code, bss, data, heap, and call stack, each holding a different category of data.
Stack data structure: LIFO, with push, pop, and top, in contrast to the random access an array gives you.
Call stack: a fixed region of memory plus a stack pointer marking the top. Each call pushes a stack frame, each return pops one, and the frames present spell out the chain of calls to the current line.
Stack frame contents: the return address, the arguments, memory for local variables, and saved registers.
Popping is cheap: only the stack pointer moves, and the old bytes are left untouched until something overwrites them.
Stack overflow: exhausting the fixed stack, either through one enormous frame such as a multi-million element local array, or through too many frames from deep or infinite recursion. The operating system terminates the program.
Heap: dynamic allocation via new, with no guarantee that consecutive allocations are adjacent, memory that persists until deleted, and a size limited by the machine rather than by a fixed segment.
Trade-off: the stack is fast, automatic, and small; the heap is slower, manual, and large.
How did you find this lesson?
Your rating helps us improve the content.
Create an account to track your progress and access interactive exercises. Already have one? Sign in.
Understanding Stack and Heap Memory - Quiz
Test your understanding of the lesson.
Practice Exercises
Stack Data Structure Simulation
Implement a simple stack data structure that demonstrates LIFO (Last-In-First-Out) behavior. Practice the core stack operations: push, pop, and top.
Lesson Discussion
Share your thoughts and questions
No comments yet. Be the first to share your thoughts!