5.1.16C Programming

Stack frames — how function calls work at the memory level

2,545 words12 min readdifficulty · medium6 backlinks

WHY does this mechanism exist?


HOW a frame is built — step by step (the calling convention)

The exact steps depend on the platform, but conceptually every call does:

CALLER:                          CALLEE (the called function):
  1. push arguments              4. push old BP (save caller's frame ref)
     (or load into registers)    5. BP = SP  (BP now marks this frame's base)
  2. CALL f  -> saves the         6. SP -= N  (carve N bytes for local vars)
     return address               7. ... do work, locals at [BP - offset] ...
  3. (execution jumps to f)       8. SP = BP        (drop locals)
                                  9. pop old BP     (restore caller's BP)
                                 10. RET -> uses return address, jumps back
Figure — Stack frames — how function calls work at the memory level

Worked Example 1 — tracing nested calls

int add(int a, int b) {   // frame: a, b, return addr, saved BP
    int s = a + b;        // local s
    return s;
}
int main(void) {
    int x = add(2, 3);    // main's frame holds x
    return 0;
}

Trace (downward stack, x86-style where return addr is pushed):

Event Stack (top = bottom of table)
in main [ x ]
push args [ x ][ 3 ][ 2 ]Why? callee needs values
CALL add [ x ][3][2][ retaddr ]Why? so add knows where to return
add prologue [ x ][3][2][retaddr][ saved BP ][ s ]Why? save caller frame + carve local
add returns 5 pop everything add added → SP back to after args; result 5 in a register
main stores x = 5

Worked Example 2 — recursion piles frames

int fact(int n) {
    if (n <= 1) return 1;        // base case
    return n * fact(n - 1);      // each call = a NEW frame
}

Calling fact(3) creates three live frames at the deepest point:

fact(3): n=3, waiting on fact(2)
fact(2): n=2, waiting on fact(1)
fact(1): n=1  -> returns 1   (deepest, returns FIRST)

Worked Example 3 — the classic dangling-pointer bug

int *bad(void) {
    int local = 42;
    return &local;   // BUG: returns address of a local
}


Recall Feynman: explain to a 12-year-old

Imagine doing homework. When you start a worksheet you grab a fresh sheet of scratch paper and write "after this, go back to page 5." You scribble your rough work on it. If that worksheet tells you to do a mini worksheet, you grab another scratch sheet on top. When you finish the mini one, you crumple its sheet and go back to the one underneath. The pile of scratch sheets is the stack, each sheet is a frame, and the "go back to page 5" note is the return address. You always finish and crumple the top sheet first.


Flashcards

What is a stack frame?
The block of stack memory for ONE active function call: its locals, saved registers, return address, and often its arguments.
How does the return address get saved on x86 vs ARM?
x86 CALL pushes it onto the stack; ARM BL (branch-and-link) stores it in the link register (LR), spilling LR to the stack only if the function calls another.
On x86/ARM, does the stack grow toward higher or lower addresses?
Lower addresses (each push decreases the stack pointer).
Why is the frame/base pointer (BP) needed when we already have SP?
SP moves during execution (e.g. pushing args); BP stays fixed so locals can be addressed at constant offsets like [BP-8].
Why must a callee save and restore the caller's BP?
BP is a shared register; saving/restoring it lets each frame anchor its own locals without losing the caller's frame.
Why is returning &local from a function a bug?
The local lives in a frame destroyed on return; the pointer dangles into reclaimed memory (undefined behavior).
Why does recursion keep separate copies of each variable?
Each call builds a fresh frame, so each call's locals occupy distinct memory.
What causes a stack overflow?
Too many nested/recursive frames (or huge locals) exhausting the stack region before frames are freed.
What discipline frees frames, and how cheap is it?
LIFO; freeing is just resetting SP back — O(1), no per-variable deletion.
Where do arguments sit relative to BP vs locals (downward stack)?
Args/return-address are ABOVE BP (BP+offset); locals are BELOW BP (BP-offset).

Connections

  • Pointers and addresses in C — why &local is dangerous ties directly to frame lifetime.
  • Recursion — each recursive call = one new frame; base case stops the pile growing.
  • Heap vs Stack memory — heap (malloc) outlives calls; stack is auto-freed on return.
  • Calling conventions (cdecl, stdcall) — who pushes/cleans args, register vs stack passing.
  • Buffer overflow & return-address smashing — security exploits that overwrite the saved return address.
  • Assembly: push, pop, call, ret, BL — the actual instructions that manage frames.

Concept Map

needs private scratch desk

stored on

grows and shrinks by

matches

contains

contains

contains

top tracked by

base tracked by

addresses locals via

on x86 pushed by

on ARM stored in

spilled to stack when

Function call

Stack frame

Call stack

LIFO discipline

Nested lifetimes

Return address

Local variables

Arguments

Stack pointer

Base pointer

Constant offsets

CALL pushes to stack

Link register

Function calls another

Hinglish (regional understanding)

Intuition Hinglish mein samjho

Socho jab tum koi function call karte ho, to computer ek chhota sa "scratch desk" banata hai us function ke liye — yahi hai stack frame. Is desk pe rehte hain: function ke local variables, arguments, aur sabse important return address (matlab "kaam khatam hone ke baad wapas kahaan jaana hai"). Jaise hi function return karta hai, uska desk turant hata diya jaata hai. Kyunki calls nested hoti hain (main → f → g), ye desks ek dusre ke upar pile ho jaate hain — LIFO style, jo sabse baad mein bana wahi sabse pehle hatta hai. Isi pile ko call stack kehte hain.

Ek important detail: return address kahaan save hota hai, ye architecture pe depend karta hai. x86 pe CALL instruction return address ko stack pe push kar deta hai. Lekin ARM pe BL (branch-and-link) us address ko ek link register (LR) mein daalta hai, stack pe nahi — aur agar wo function aage koi aur function call kare (jisse LR overwrite ho jaaye), tabhi prologue LR ko stack pe spill karta hai. Stack pointer (SP) stack ke top ko point karta hai, aur base pointer (BP) current frame ke andar ek fixed reference deta hai taaki locals ko BP-8 jaise constant offset se access kar sako, chahe SP idhar-udhar move karta rahe. Aur yaad rakho: zyaadatar CPUs pe stack neeche ki taraf grow karta hai (push pe address kam hota hai).

Do cheezein zaroor yaad rakho. Pehla: recursion mein har call ka apna alag frame hota hai, isliye har n ki alag copy hoti hai — bahut deep recursion = stack overflow. Doosra (bahut common bug): kabhi bhi local variable ka address return &local mat karo, kyunki return hote hi wo frame destroy ho jaata hai aur pointer ek dead memory ko point karta reh jaata hai. Ye samajhna interviews aur debugging dono mein game-changer hai — kyunki ab tum exactly bata sakte ho ki "ye variable kahaan, kab tak, aur kyun zinda rehta hai."

Go deeper — visual, from zero

Test yourself — C Programming

Connections