The Stack and Function Calls
Call frames, recursion, stack overflow, and the lifetime of local variables
Every time you call a function in C, the runtime pushes a stack frame that holds the function's parameters, local variables, and the return address it needs to come back to its caller. When the function returns, that frame is popped — and every local in it is gone.
This page makes that mechanism concrete enough that you can predict how recursion uses memory and where local pointers stop being safe to read.
Anatomy of a stack frame
A typical frame contains:
- the return address (where to resume in the caller)
- the saved frame pointer (so debuggers can walk the call chain)
- the function's parameters (or in many ABIs, copies of register arguments)
- the function's local variables
- on some platforms, padding to keep alignment
The stack typically grows from high addresses to low — every call subtracts from the stack pointer, every return adds back. On WASI the direction is the same conceptually even if the linear memory layout differs.
See the stack with addresses
You can see the call chain by printing addresses of local variables.
The addresses of x in main, middle, and inner should be
decreasing (or sometimes increasing — the direction is
platform-dependent), and the difference between consecutive frames
shows roughly how big each frame is.
Recursion uses the stack
Each recursive call pushes a new frame. A function that recurses n
times uses n frames at peak — so a recursion depth limit is really
a stack size limit.
fib(40) would push roughly 2 × 10⁸ frames in total over its run
(though only ~40 are alive at once because the calls return between
them). The total work explodes; the peak stack depth is only n.
Stack overflow
When recursion is too deep — or a function declares a huge local array — you hit the end of the stack region and the program crashes with a stack overflow.
Beware huge stack arrays
A local declaration like char buf[16 * 1024 * 1024]; allocates 16 MB
on the stack. On many platforms the entire stack is only 1 MB, and
this will crash before main runs a single line. Use the heap
(malloc) for large buffers.
Iteration vs recursion — same algorithm, different memory shape
Many recursive algorithms have iterative equivalents that use O(1) stack instead of O(n). Compare:
Both compute the same value. The iterative version is friendlier on the stack and easier for an optimizer to reduce to a tight loop.
Tail calls
In return f(...) — where the recursive call is the very last
thing the function does — a sufficiently aggressive optimizer can
reuse the current frame instead of pushing a new one. This is
called tail-call optimization (TCO). C compilers will sometimes
perform it at higher optimization levels but the language does not
guarantee it.
// Tail-recursive form of factorial: every recursive call is the function's
// last action, with the accumulated product passed as a parameter.
long fact_tail(int n, long acc) {
if (n <= 1) return acc;
return fact_tail(n - 1, n * acc);
}If you depend on tail calls (e.g. for an interpreter loop), measure
with -O2. Otherwise prefer an explicit loop.
Lifetimes: when a local stops being valid
The single most important stack rule:
A local variable lives only between the matching
{and}. Any pointer to it becomes invalid the instant control leaves that scope.
The pattern that is safe: write into a buffer the caller owns.
This caller owns the buffer idiom is everywhere in C APIs: think
fgets(buf, sizeof buf, stdin) or snprintf(buf, sizeof buf, ...).
Practice: iterative power
Implement long my_pow(int base, int exp) for non-negative exp using a loop — not recursion. my_pow(2, 10) must return 1024. The provided main prints results.
Practice: greatest common divisor (iterative)
Implement int gcd(int a, int b) using the iterative Euclidean algorithm so that it uses O(1) stack frames regardless of input size. The provided main calls it for three pairs.
Test your understanding
A function declares char buf[8 * 1024 * 1024]; as a local variable on a platform with a 1 MB stack. What is the most likely outcome?
The compiler silently moves the array to the heap.
The OS automatically grows the stack to fit.
The variable is allocated successfully but slowly.
The program runs out of stack and crashes (stack overflow).
What is wrong with this function?
char *greeting(void) {
char buf[16] = "Hello";
return buf;
}
It allocates too few bytes for "Hello".
It returns a string in the wrong encoding.
It forgets to null-terminate.
It returns a pointer to a local array whose stack frame is destroyed the moment the function returns; the caller dereferences a dangling pointer.
Compared to the recursive fact_rec(n), why is fact_iter(n) usually preferred in C?
It computes a different value (the iterative version is more accurate).
The compiler can vectorize iteration but never recursion.
It uses a constant number of stack frames regardless of n, eliminating the risk of stack overflow for large inputs.
The recursive version is undefined behavior in standard C.