The Runtime Theory
ApplicationFoundationsexecution

Trace a Function Call in C++

Follow the call stack as main calls a function, a local variable is created, and the stack frame is pushed and popped.

The Runtime Theory Team1 min read10 steps

trace spine

  1. 01 main() starts; stack pointer (RSP) at initial position
  2. 02 main() declares local variable x=42; x pushed onto the stack frame
  3. 03 main() calls foo(); return address pushed onto the stack
  4. 04 foo() receives control; new stack frame pushed for foo
  5. 05 foo() declares local variable y=10; y stored in foo's frame
  6. 06 foo() calls bar(5); return address pushed, bar's frame pushed
  7. 07 bar() computes result=10; stored in bar's frame
  8. 08 bar() returns; bar's frame popped; return value passed to foo
  9. 09 foo() receives result=10 in local variable; foo's frame popped
  10. 10 main() receives return value; main's frame cleaned up by runtime
▸ On this page

Trace: A Function Call in C++

The Call Stack

plaintext
High Address
┌─────────────────┐
│ bar() frame     │  ← SP (top of stack)
│   y = ?         │
├─────────────────┤
│ foo() frame     │
│   y = 10        │
│   return addr   │
├─────────────────┤
│ main() frame    │
│   x = 42        │
│   return addr   │
└─────────────────┘ Low Address

What Happens at Each Step

  1. main() starts — the runtime creates the initial stack frame for main(). The stack pointer (RSP) points to the top of this frame.

  2. Local variable x = 42 — x is allocated in main()'s stack frame. The compiler knows the offset from the stack pointer (e.g., x is at RSP + 4).

  3. foo() is called — before transferring control, the CPU pushes the return address (the instruction after the call instruction) onto the stack. The call instruction also jumps to foo()'s code.

  4. foo()'s prologue — foo()'s first instructions (the prologue) set up a new stack frame: it moves the old base pointer (RBP) and sets a new one.

  5. Local variable y = 10 — y is allocated in foo()'s stack frame at a known offset.

  6. bar(5) is called — the argument 5 is pushed, then the return address, then bar()'s frame is set up. This is stack growth — more frames are added on top.

  7. bar() executes — bar() computes result = 5 * 2 = 10 and stores it.

  8. bar() returns — the ret instruction pops the return address from the stack, jumps back to foo(), and bar()'s frame is effectively discarded (the stack pointer moves back).

  9. foo() returns — same process. The stack pointer moves back, freeing foo()'s frame.

  10. main() finishes — the runtime cleans up main()'s frame and the process exits.

Key Takeaways

  • Each function call pushes a stack frame (also called an activation record).
  • The stack grows downward in memory (from high addresses to low).
  • Function arguments and return addresses are stored on the stack.
  • When a function returns, its stack frame is popped — all its local variables become invalid.
  • A stack overflow occurs when the stack grows too large (e.g., infinite recursion).

Not started

Sign in to save your learning progress.

Sign in to save