8.3 Runtime Environments: Activation Records, Storage Allocation & Parameter Passing
π‘ Core Intuition: The 3-Second Mental Modelβ
π³ The Everyday Analogy: The Office Desk Stack and Call Logsβ
Imagine an executive managing an engineering firm:
- The Call Stack: Whenever the manager delegates a sub-task to a specialist, the specialist arrives and places a dedicated folder (Activation Record / Stack Frame) on top of the desk. When finished, they remove their folder, exposing the manager's desk exactly as it was left.
- Dynamic Link (The Return Phone Number): A bookmark inside the folder noting: "When I finish this sub-task, hand the desk back to whoever called me (the runtime caller)."
- Static Link (The Corporate Org Chart): A pointer pointing to the department director who authored the departmental rulebook (lexical enclosing scope), regardless of who dialed the phone!
- Call by Value vs Reference: Handing the specialist a photocopied paper sheet (Call by Value) versus handing them the physical keys to the master filing cabinet (Call by Reference). If the specialist spills coffee on the photocopy, the master cabinet is safe. If they spill coffee in the master cabinet, the company's ground-truth data is permanently altered!
π» Bridging to Computer Science: Virtual Address Spaces & Call Frame Lifecyclesβ
During compilation, source variables and procedures are mapped onto a target architecture's virtual memory space:
- The compiler partitions the virtual address space into distinct segments: Code (Text), Static Data, Heap, and Stack.
- Dynamic procedure invocations require allocating an Activation Record (AR) or Stack Frame to store parameters, return addresses, saved registers, local variables, and temporaries.
- Resolving non-local identifiers requires either pointer chains (Static Links) or an indexed pointer table (Displays).
- Transmission of arguments across procedure boundaries is governed by parameter-passing protocols: Call by Value, Call by Reference, Call by Value-Result, and Call by Name.
πTable of Contents
- ποΈ Architectural Foundations: Storage Allocation Strategies (Static vs Stack vs Heap)
- π§± Activation Record (Stack Frame) Anatomy
- π Non-Local Variable Resolution: Static Links vs Displays
- π Parameter Passing Mechanisms
- π Real-World Production Context: System V AMD64 ABI & Calling Conventions
- π Numerical Applications & Worked Examples
- π― Exam & Interview Pitfall Check
ποΈ Architectural Foundations: Storage Allocation Strategies (Static vs Stack vs Heap)β
The compiler organizes the target machine's virtual address space into specialized segments managing three distinct storage classes:
Process Virtual Address Space Architecture
Standard 4-Section memory layout from High Memory down to Low Memory
Stack Segment (Grows Downward β¬)
LIFO call stack containing activation records, local variables, and saved CPU registers.
Heap Segment (Grows Upward β¬)
Dynamic memory allocation managed at runtime (malloc, free, new, garbage collector).
Data / BSS Segment (Fixed Size)
Global variables, static constants, and uninitialized BSS buffers fixed at compile time.
Code / Text Segment (Base 0x00000000)
Read-only compiled machine instructions with hardware page protection.
Comparative Storage Allocation Paradigmsβ
Storage Allocation Paradigms: Static vs Stack vs Heap
Contrasting allocation timing, lifetime predictability, and execution overhead
Static Storage Allocation
- β’Memory addresses are permanently bound at compile/link time.
- β’Zero runtime allocation or deallocation overhead.
- β’Variables retain values across multiple procedure invocations.
- β’Cannot support recursion: all calls share identical memory cells!
Stack-Based Dynamic Allocation
- β’Allocated and freed in strict Last-In-First-Out (LIFO) call order.
- β’Naturally supports unbounded recursive function invocations.
- β’Allocated at negligible cost via single stack pointer adjustments.
- β’Lifetimes are strictly bound to function entry and exit points.
π§± Activation Record (Stack Frame) Anatomyβ
An Activation Record is a contiguous block of memory allocated on the call stack every time a procedure or function is invoked. It contains all runtime data necessary to manage procedure execution and resume the caller upon return.
The 7 Canonical Fields of an Activation Record
Stack frame anatomy from High Memory (caller context) to Low Memory (top of stack)
1. Actual Parameters & Arguments
Values or pointer references pushed by the caller prior to function invocation.
2. Returned Value Slot
Dedicated memory reserved for the callee to return its computed result to the caller.
3. Control Link (Dynamic Link)
Pointer to the base of the calling procedure's activation record (Saved Frame Pointer RBP).
4. Access Link (Static Link)
Pointer to the activation record of the lexically enclosing procedure in source text.
5. Saved Machine Status
Return address (Program Counter RIP) and callee-saved CPU general-purpose registers.
6. Local Variables & Data Arrays
Scalars, structures, and arrays declared locally within the procedure body.
7. Temporaries & Evaluation Stack
Temporary locations for evaluating complex arithmetic expressions (Top of Stack RSP).
π Field Mechanics & Responsibilitiesβ
- Actual Parameters: Pushed by the caller or passed in registers; formal parameters inside the callee reference these offsets.
- Returned Value: Memory location where the callee places its output so the caller can read it immediately following stack unwinding.
- Control Link (Dynamic Link): Points to the activation record of the caller. When the callee returns, the runtime restores the stack frame pointer via:
- Access Link (Static Link): Points to the activation record of the immediate lexical parent in nested block-structured languages (Pascal, Ada, Python).
- Saved Machine Status: Contains the return instruction pointer (
%rip) and contents of registers that must remain invariant across the call. - Local Data: Stores automatic local variables whose sizes are known at compile time.
- Temporaries: Temporary variables generated during intermediate three-address code evaluation when available CPU registers are exhausted.
π Non-Local Variable Resolution: Static Links vs Displaysβ
When a procedure accesses a variable declared in an outer enclosing procedure , how does the runtime resolve the address of ?
1οΈβ£ Static Links (Access Links)β
Each activation record contains a pointer to the most recent activation record of its immediate lexical parent.
- If procedure is nested at depth , and accesses variable declared at nesting depth :
- The runtime must traverse static links to reach 's frame.
- The variable is accessed using a fixed compile-time offset within that frame:
- Complexity Trade-Off: Accessing deeply nested variables requires pointer dereferences, introducing memory traversal latency.
2οΈβ£ Display Registersβ
To achieve constant-time non-local access, compilers maintain an auxiliary array of pointers called a Display:
- always stores a pointer to the most recent activation record of the procedure at lexical nesting depth .
- Accessing any non-local variable at depth requires a single array lookup and offset addition:
- Maintenance Cost: When a procedure at depth is entered, it saves the old value of in its activation record and updates to its own frame pointer. On return, it restores the previous value.
π Parameter Passing Mechanismsβ
How arguments are passed between caller and callee defines a programming language's runtime semantics and data-sharing properties:
The 4 Foundational Parameter Passing Protocols
From pass-by-copy safety to call-by-name lazy evaluation
Call by Value
Caller evaluates actual expression; passes a distinct local copy to callee.
Call by Reference
Caller passes the actual memory address (l-value). Callee mutates original cell directly.
Call by Value-Result
Pass copy in on call; write modified copy back to caller variable upon procedure return.
Call by Name
Textual macro substitution evaluated lazily on every access via an anonymous helper thunk.
1οΈβ£ Call by Value (Pass by Copy)β
- The caller evaluates the actual parameter expression and places its r-value into the callee's formal parameter slot.
- Assignments inside the callee modify only the callee's local memory cell.
- The caller's variables remain strictly isolated and protected from side effects.
2οΈβ£ Call by Reference (Pass by Pointer / Address)β
- The caller passes the memory address (l-value) of the actual variable.
- Any assignment to the formal parameter mutates the caller's actual variable immediately.
- Enables side effects and avoids copying large structures, but introduces aliasing risks when multiple parameters point to the same variable.
3οΈβ£ Call by Value-Result (Copy-In / Copy-Out)β
- A two-phase hybrid mechanism:
- Copy-In: On procedure entry, actual values are copied into local formal variables.
- Local Execution: Operations modify the local copies.
- Copy-Out: Upon return, final values of formal parameters are copied back into the caller's actual variables.
- Aliasing Anomaly: If two formal parameters represent the same caller variable (e.g.,
swap(x, x)), the final value of depends strictly on the compiler's copy-out evaluation order (left-to-right vs. right-to-left)!
4οΈβ£ Call by Name (Algol 60 Lazy Substitution)β
- The actual argument expression is textually substituted into the callee's body wherever the formal parameter occurs.
- Evaluated lazily on every single access.
- Implemented by compiling an anonymous parameterless subroutine called a Thunk that evaluates the argument expression within the caller's lexical context.
π Real-World Production Context: System V AMD64 ABI & Calling Conventionsβ
In modern 64-bit production environments (Linux, macOS, BSD on x86-64):
- Register Argument Passing: Allocating and reading arguments on memory stacks incurs memory bus stalls. The System V AMD64 ABI mandates passing the first 6 integer or pointer arguments directly in high-speed hardware CPU registers:
RDI(First Argument)RSI(Second Argument)RDX(Third Argument)RCX(Fourth Argument)R8(Fifth Argument)R9(Sixth Argument)
- Arguments 7 and beyond are pushed onto the stack in right-to-left order.
- Floating-point arguments are passed in vector registers
XMM0throughXMM7. - 16-Byte Stack Alignment: The ABI dictates that
RSP + 8must be a multiple of 16 immediately before acallinstruction is executed. Thecallinstruction pushes the 8-byte return address, ensuring that upon callee entry,RSPis precisely 16-byte aligned for vector SSE/AVX memory operations.
π Numerical Applications & Worked Examplesβ
Example 1: Output Comparison Across All Parameter Passing Modesβ
Consider the following program written in pseudo-C syntax:
int x = 10;
void swap(int a, int b) {
x = x + 1;
a = a + 5;
b = b + 10;
}
int main() {
x = 10;
swap(x, x);
print(x);
return 0;
}
Determine the printed value of variable under:
- Call by Value
- Call by Reference
- Call by Value-Result (under both left-to-right and right-to-left copy-out orders)
Step-by-Step Analytical Trace:β
-
Call by Value Trace:
- In
main(): . swap(x, x)is invoked:- Formal parameter , (isolated local stack cells).
- Inside
swap():- Global becomes .
- (local to
swap). - (local to
swap).
- On return:
- Local variables and are deallocated.
- Global remains .
- Printed Result:
11
- In
-
Call by Reference Trace:
- In
main(): . swap(x, x)is invoked:- Both formal parameters and receive the address of ().
- Thus, , , and global are exact aliases for the same physical memory cell!
- Inside
swap():- Line 1: .
- Line 2: .
- Line 3: .
- On return:
- Memory cell for contains .
- Printed Result:
26
- In
-
Call by Value-Result (Copy-In / Copy-Out) Trace:
- In
main(): . - On entry to
swap(x, x):- Copy-In: , .
- Inside
swap():- Global becomes .
- .
- .
- On return (Copy-Out Phase):
- Case A: Left-to-Right Copy-Out:
- First restore .
- Next restore (overwrites 15!).
- Final .
- Case B: Right-to-Left Copy-Out:
- First restore .
- Next restore (overwrites 20!).
- Final .
- Case A: Left-to-Right Copy-Out:
- Notice that the intermediate update to global () was completely obliterated during the copy-out phase!
- In
Example 2: Jensen's Device and Call by Name Dynamic Re-Evaluationβ
Consider the following program evaluated under Call by Name:
int i = 1;
int a[3] = {10, 20, 30}; // 1-indexed array: a[1]=10, a[2]=20, a[3]=30
void modify(int index, int val) {
index = index + 1;
val = val + 5;
}
int main() {
modify(i, a[i]);
print(a[1], a[2]);
return 0;
}
Determine the final values of and printed by main().
Step-by-Step Analytical Trace:β
Under Call by Name, formal parameter names are textually substituted with the caller's expressions via thunks:
indexis bound to the expressioni.valis bound to the expressiona[i](evaluated dynamically on each access!).
- Inside
modify():- Statement 1:
index = index + 1:- Expands to: .
- Since was , variable becomes .
- Statement 2:
val = val + 5:- Expands to: .
- Dynamic Thunk Re-evaluation: Because variable is now , the expression evaluates to , not !
- Therefore: .
- Statement 1:
- In
main():- was never modified: .
- was modified: .
- Printed Result:
10, 25
Example 3: Static Link Traversal vs Display Resolutionβ
Suppose a program has four nested procedures: (depth 1), (depth 2), (depth 3), and (depth 4), where is lexically enclosed in , in , and in .
Suppose during execution, references variable declared in .
- How many static link dereferences are required to access ?
- How many memory accesses are required using a Display?
Step-by-Step Analytical Trace:β
- Static Link Resolution:
- Number of links to traverse .
- Memory operations:
- Dereference 's static link to reach 's frame.
- Dereference 's static link to reach 's frame.
- Dereference 's static link to reach 's frame.
- Access at .
- Total pointer traversals: 3 static link traversals ( memory reads total).
- Display Resolution:
- Display lookup: Read (stores 's frame pointer directly).
- Access at .
- Total accesses: 1 Display read + 1 memory read ( constant time).
π― Exam & Interview Pitfall Checkβ
Question 1: "Why can a language that uses purely static memory allocation not support recursion?"
Key Focus Points:
- In static allocation, each variable and procedure has a single, fixed memory location bound at compile or link time.
- If a procedure calls itself recursively, the new activation writes its parameters and local variables into the exact same memory cells as the caller activation, destroying the caller's state.
- Supporting recursion requires a dynamic stack, where each activation receives its own distinct activation record on the call stack.
Question 2: "What is the difference between a Dynamic Link (Control Link) and a Static Link (Access Link)?"
Key Focus Points:
- Dynamic Link: Points to the activation record of the caller in time. It reflects the dynamic call graph () and is used to restore the caller's stack frame upon return.
- Static Link: Points to the activation record of the lexically enclosing procedure in source text space. It reflects the static syntactic nesting structure and is used to access non-local variables.
Trap 1: Returning the address of a local stack variable in C/C++.
Answer: Catastrophic dangling pointer. When a function returns, its activation record is popped and its stack memory is reclaimed. Returning &local_var produces a pointer to unallocated memory. Any subsequent function call will overwrite that memory segment, causing erratic data corruption or segmentation faults.
Trap 2: Assuming Call by Value-Result is identical to Call by Reference.
Answer: False under aliasing. When two parameters reference the same variable (or one parameter references a global variable modified in the procedure), Call by Reference mutates the variable immediately in place. Call by Value-Result works on isolated local copies and writes them back only upon return. If copy-back order changes, the resulting value changes.
Trap 3: Stack smashing via buffer overflow.
Answer: Security vulnerability. In an activation record, local array buffers are situated adjacent to the saved frame pointer and return address. Unbounded writes (e.g. strcpy) overwrite the saved return address. When the function executes ret, the CPU jumps to attacker-controlled instructions. Modern compilers mitigate this using Stack Canaries (-fstack-protector).