What enables a recursive function to have independent copies of its local variables for each level of nesting, even though all levels execute the same code?
AThe compiler generates a separate copy of the function code for each recursive invocation
BEach function call creates a new activation record on the call stack, giving each invocation its own independent storage for locals and parameters
CLocal variables are stored in a global hash table indexed by call depth
DRegisters are multiplied — the CPU allocates a fresh register file per call
The call stack is the key. Every call to a function — including a recursive call — pushes a new activation record onto the stack. Each record has its own copy of the local variables at fixed offsets from the frame pointer. Two recursive calls at the same nesting level have two completely separate frames, so their locals are independent. When a call returns, its frame is popped, revealing the caller's frame with its own locals intact.
Question 2 Multiple Choice
A function allocates a local character array `char buf[8]` and an attacker writes 20 bytes into it via an unchecked input. What does activation record layout explain about the consequence?
ANothing; the operating system prevents writes beyond the declared array size
BThe extra bytes overwrite adjacent regions of the activation record — potentially including the saved return address — redirecting control flow when the function returns
CThe local array silently expands to accommodate the overflow
DThe extra bytes overwrite heap memory, not stack memory
In the activation record, `buf` is allocated at a fixed negative offset from the frame pointer, and the saved return address is at a known positive offset. Writing beyond the end of `buf` advances into the rest of the frame. If the overflow reaches the saved return address slot, the attacker controls where execution jumps when the function's epilogue executes its return instruction. This is the classic stack-smashing / buffer overflow attack, and the activation record layout is precisely what makes it possible.
Question 3 True / False
Each function call pushes a new activation record onto the call stack, which is why recursive functions correctly maintain independent copies of local variables at every nesting level.
TTrue
FFalse
Answer: True
The stack discipline is exactly what makes recursion work. Each call to a function — including recursive self-calls — gets its own frame with its own storage. The frame is only released when the function returns (epilogue pops it). Two concurrent activations of the same function have two distinct frames at two different stack addresses, so their locals never conflict. This is not a language-level abstraction — it is a direct consequence of how the compiler generates prologue and epilogue code.
Question 4 True / False
Returning a pointer to a local variable is safe as long as the caller dereferences it before calling any other function, since the stack has not yet been reused.
TTrue
FFalse
Answer: False
The activation record is popped the moment the function returns, reclaiming its stack memory. From that instant, the memory is logically free and will be overwritten by the prologue of any subsequent function call — regardless of timing. Accessing a local variable via a returned pointer is always undefined behavior; there is no safe window between the return and the next call, because even the act of calling another function will reuse that stack space.
Question 5 Short Answer
Explain why tail-call optimization allows a recursive function to run in constant stack space, using the concept of activation records.
Think about your answer, then reveal below.
Model answer: In a tail call, the calling function has completely finished its own work and will return the callee's result directly — it has no further use for its own activation record. Rather than pushing a new frame on top of the existing one, the compiler reuses (overwrites) the current frame with the new call's data. The stack depth therefore stays constant regardless of how many recursive calls occur. Without TCO, each call would push a new frame, growing the stack linearly with call depth until a stack overflow.
Tail-call optimization transforms recursion into iteration at the machine level without changing the source-level semantics. It is why functional languages like Scheme guarantee TCO — unbounded recursion is the natural idiom, and without TCO it would always stack-overflow. The activation record concept makes it clear why the optimization is valid: if you don't need the current frame anymore, there is no reason to preserve it.