Introduction
The code a compiler generates runs inside a runtime environment: an agreed layout of memory and an agreed protocol for calls and returns. The compiler decides where each name lives (a fixed address, a slot in a stack frame, or a block in the heap), how a procedure finds its parameters and its nonlocal variables, and who saves which registers. These decisions are fixed at compile time, but they describe what happens at run time.
The central data structure is the activation record (stack frame): one per live call. Examination questions ask which fields it holds, how control and access links are set, how many records are on the stack at a given moment, and which storage class suits which kind of data.
Prerequisites
- Functions and Parameter Passing: calls, returns, local variables and parameters at the source level.
- Lexical scope and nested blocks (see the note Parameter Passing and Static vs Dynamic Scope for the dynamic-scope contrast).
Core concepts
- Procedure versus activation. A procedure is program text; an activation is one execution of it. A recursive procedure can have many live activations at once.
- Lifetime. The span of execution from a call's start to its return. Activations of procedures in a sequential language are properly nested: if is called during , then returns before does. This is why a stack works.
- Activation tree. Each node is an activation, its children are the calls it makes in order. The calls run in preorder of the tree and return in postorder. The live activations at any moment are exactly the path from the root to the current node, and they are the records on the control stack.
- Static allocation. Addresses fixed by the compiler: code, global and static variables, constants. Needs no run-time work but supports neither recursion nor data whose size is known only at run time.
- Stack allocation. Activation records pushed at calls and popped at returns. Supports recursion; local data dies with its activation.
- Heap allocation. Blocks obtained and released in any order (
malloc/free,new), for data that outlives the call that created it or whose size is unknown at compile time. - Nesting depth. In languages with nested procedure declarations, the outermost procedure has depth 1 and a procedure declared directly inside one of depth has depth .
How it works
Storage layout
A typical run-time image places the target code in the low area, then static data, then the heap, which grows toward higher addresses. The stack starts at the top and grows toward lower addresses. Heap and stack share the free space between them, so neither needs a fixed size limit.

In C terms: global and static variables and string literals are static; ordinary locals and parameters are on the stack; objects from malloc are in the heap, while the pointer that holds their address may be a stack local.
The activation record
A general activation record holds, from the caller's side to the callee's side:
| Field | Purpose |
|---|---|
| Actual parameters | Values (or addresses) supplied by the caller; often passed in registers instead |
| Returned value | Space for the result, near the caller so that it can find it |
| Control link | Points to the caller's activation record (the dynamic link) |
| Access link | Points to the record of the lexically enclosing procedure (the static link); needed only with nested procedures |
| Saved machine status | Return address and registers that must be restored |
| Local data | Locals whose size is known at compile time |
| Temporaries | Intermediate values from expression evaluation |
Variable-length data, such as an array whose size depends on a parameter, is placed beyond the fixed part, and the fixed part holds a pointer to it, so every fixed field keeps a constant offset.
Calling and return sequences
The calling sequence allocates the record and fills it; the return sequence restores state. A common division of work:
- The caller evaluates the actual parameters and stores them in the callee's record or registers.
- The caller stores the return address and the old frame pointer (control link) and sets the new frame pointer.
- The callee saves registers and other status, then initialises locals and begins.
- On return, the callee places the result where the caller expects it, restores registers and the frame pointer, and jumps to the return address.
- The caller then reads the returned value; the record is popped.
Fields that the caller fills go first (nearest the caller), so that the caller can fill them without knowing the callee's local layout. A frame pointer typically points to the end of the fixed-length fields so that locals are at known offsets from it.
Example 1: counting activations
For fib(n) = n if , else fib(n-1) + fib(n-2), the activation tree of fib(4) has 9 nodes and 5 leaves. Its preorder (the order of calls) is fib(4), fib(3), fib(2), fib(1), fib(0), fib(1), fib(2), fib(1), fib(0). The deepest root-to-leaf path is fib(4), fib(3), fib(2), fib(1), so at most 4 activation records of fib are on the stack at once. For fib(5) there are 15 activations and a maximum depth of 5. The number of records alive at once is the maximum node-depth (edge height plus 1), not its size.
Example 2: an uneven activation tree
Let for and otherwise, with the left call made first. For :
- Calls (preorder): , , , , , , , , , , : 11 activations.
- Returns (postorder): , , , , , , , , , , .
- Maximum depth: the path , , , , , so at most 5 records at once.
- Value: , , , .
When the last activation, called from the second , is running, the stack holds only : the whole subtree of has already returned. A question asking "how many records are on the stack when runs for the second time" wants this root-to-node path, 3, not the number of calls made so far, 11.
Access to nonlocal data
Without nested procedures (C). Every name is either global, at a static address, or local to the current activation, at an offset from the frame pointer. No access link is needed.
With nested procedures (Pascal, ML, nested functions). Consider sort (depth 1) declaring readarray, exchange and quicksort (depth 2), with partition (depth 3) declared inside quicksort. Under lexical scope, partition may use variables of quicksort and sort. The record of the enclosing procedure is reached through the access link, which in the direct, non-escaping nested-procedure model here points to the appropriate active lexical parent. For passed or escaping nested functions, a closure retains the defining lexical environment; it need not be the most recently active parent.
Setting the access link (direct, non-escaping nested calls only). When a procedure at depth calls at depth :
- If ( is declared directly inside ), 's access link points to 's record.
- If , follow access links starting from 's record; the record reached is 's lexical parent's, and 's access link points there. (Recursion, , follows one link.)
To use a variable declared at depth from code at depth , follow access links and then use the variable's offset in that record. Both counts are known at compile time.
Example 3: the stack during partition and exchange. Let sort call quicksort(1,9), which calls quicksort(1,3), which calls partition(1,3), which calls exchange(1,3). There are five records and four control links, each pointing to the record just below. The access links are:
| Record | Depth | Access link points to |
|---|---|---|
sort | 1 | none |
quicksort(1,9) | 2 | sort |
quicksort(1,3) | 2 | sort (recursive call: one link followed from quicksort(1,9)) |
partition(1,3) | 3 | quicksort(1,3) (declared inside its caller) |
exchange(1,3) | 2 | sort ( links followed from partition) |
The control links reflect who called whom; the access links reflect who encloses whom in the program text. They differ for quicksort(1,3) and exchange(1,3).
Using a nonlocal. If the array a is declared in sort (depth 1), then exchange (depth 2) reaches it by following access link, and partition (depth 3) by following links: first to quicksort(1,3), then to sort. The same counts hold for every activation of those procedures, whatever the call history.

Displays
A display is an array with one pointer per nesting depth: points to the most recent activation at depth . A procedure at depth reaches a variable at depth in one step through , instead of following links. On a call to a procedure at depth , its record saves the old and is set to the new record; the return restores the saved value. In Example 3, after the call to exchange, points to sort and to exchange, while still points to partition; exchange uses only and , which are exactly its static chain.
Heap management
The memory manager hands out and takes back heap blocks. Strategies such as best fit and first fit trade search time against fragmentation; adjacent free blocks are coalesced. Manual deallocation can leave dangling pointers (use after free) and memory leaks (blocks never freed).
Related material. Automatic garbage collection (reference counting, mark-and-sweep, copying collectors) reclaims unreachable heap blocks; it is treated in Dragon Book Sections 7.5 and 7.6.
Important properties and observations
- Stack allocation works because lifetimes nest. The live activations are the path from the root of the activation tree to the current call.
- Maximum simultaneous records equal activation-tree edge height plus 1 (node-depth), not the number of calls.
- Control link versus access link. The control link follows the call history and is used on return; the access link follows the program's nesting and is used to reach nonlocal names.
- Static allocation cannot support recursion: one fixed slot per local cannot hold the values of several live activations.
- Returning a pointer to an automatic local whose lifetime ends on block exit leaves a dangling pointer in C: the activation record is popped and its storage may be reused. A static-duration local remains alive and is excluded.
- Access-link hop counts are compile-time constants, for a use and for a call with .
Common misconceptions.
- "The access link points to the caller." Only when the callee is declared directly inside the caller.
- "Heap means global." A heap object can be reached only through pointers; the variables holding those pointers can be anywhere.
- "Each procedure has one activation record." Each activation has one; a recursive procedure has several at once.
GATE examination perspective
- Which storage for which entity. Globals and statics are static; locals and parameters are on the stack; dynamically created objects are in the heap; recursion requires stack (or heap) allocation of locals.
- Activation-tree counting. Count calls (tree size), maximum simultaneous records (edge height plus 1), or the order of calls (preorder) and returns (postorder) for a recursive function.
- Records on the stack at a given call. These are the path from the root to that call (Example 2: 3 records when the second runs, after 11 calls).
- Fields of an activation record. Know what the control link, access link, saved status and returned-value fields hold, and that the access link is unnecessary in C.
- Access links with nesting depths. Given nested procedures and a call chain, find where each access link points and how many links are followed.
- Displays. As the constant-time alternative to access-link chains.
Summary
Sources
- A. V. Aho, M. S. Lam, R. Sethi, J. D. Ullman, Compilers, Principles, Techniques, and Tools, 2nd ed., Pearson Addison-Wesley, 2007: Section 7.1 (storage organisation; static versus dynamic storage allocation), Section 7.2 (activation trees, activation records, calling sequences, variable-length data on the stack), Section 7.3 (data access without nested procedures, nesting depth, access links, manipulating access links, displays), Section 7.4 (heap management, reducing fragmentation, manual deallocation requests), Sections 7.5 and 7.6 (garbage collection: reachability, reference counting, mark-and-sweep, copying collectors; related material only).