By the end of this chapter you'll be able to…

  • 1State the two questions the runtime environment answers for every name
  • 2Name the four storage regions and the lifetime each holds
  • 3Explain why static allocation cannot support recursion
  • 4List the components of an activation record
  • 5Distinguish the control link from the access link
  • 6Explain why C needs no access link
  • 7Explain why locals are addressed as offsets from a frame pointer
  • 8Divide the call sequence between caller and callee
  • 9Explain why shared responsibilities are placed in the callee
  • 10State the logic behind the caller-saved and callee-saved split
  • 11Distinguish static from dynamic scope with a program that separates them
  • 12State why almost every modern language uses static scope
  • 13Reach a non-local variable using access links
  • 14Set an access link correctly given the relative nesting depths
  • 15Compare access links with a display on access and maintenance cost
  • 16Distinguish call by value, reference, value-result and name
  • 17Use aliasing as the discriminating test between mechanisms
  • 18Explain why C has only call by value
  • 19Distinguish a dangling reference from a memory leak
  • 20Explain why reference counting cannot reclaim a cycle
  • 21Explain why mark and sweep can
  • 22Compare copying and generational collection with mark and sweep
💡
Why this chapter matters in GATE
The runtime environment looks like a collection of unrelated conventions and is really the answer to two questions asked of every name: where does its storage live, and how long does it last? A global lives in the static area for the whole program, a local lives on the stack for one activation, and a dynamically allocated object lives on the heap until something releases it. Those three answers generate the three storage areas, and every mechanism here manages one of them. The second organising fact is that the stack works because call nesting is last-in-first-out while the heap needs machinery because allocation lifetimes are not. A procedure called second returns first, so a stack matches the discipline exactly, whereas heap objects are freed in no predictable order and therefore need a free list, a collector, or both. So the diagnostic question for any storage feature is which lifetime it serves.

Before you start — revise these

🔗
Machine Instructions & Addressing Modes
Activation records, the stack discipline and base-plus-offset addressing of locals are introduced there from the hardware side.
🔗
Programming in C & Recursion
Storage classes, frame lifetime and the reason returning a local's address fails are the same facts examined from the language side.
🔗
Lexical Analysis, Parsing & Syntax-Directed Translation
The symbol table built during parsing is what records the scope and offset information this chapter's mechanisms consume.

Runtime Environments

The runtime environment is the machinery a compiler generates to manage storage while the program runs. It looks like a collection of unrelated conventions and is really the answer to two questions asked of every name.

Where does this name's storage live, and how long does it last?

A global variable lives in the static area for the whole program. A local lives on the stack for one activation. A dynamically allocated object lives on the heap until something releases it. Those three answers generate the three storage areas, and every mechanism in this chapter exists to manage one of them.

The second organising fact is that the stack works because call nesting is last-in-first-out and the heap needs machinery because allocation lifetimes are not.

A procedure called second returns first, so a stack matches the discipline exactly and needs no bookkeeping beyond a pointer. Heap objects are freed in no predictable order, which is why the heap needs a free list, or a garbage collector, or both.

So the diagnostic question for any storage feature is which lifetime it serves. Recursion needs per-activation storage, hence the stack. Closures outliving their creator need heap storage. Static variables persisting across calls need the static area.

1. Storage Organisation

Four regions divide the address space, and each holds a different lifetime.

RegionContentsLifetimeManaged by
CodeInstructionsWhole programFixed at load
StaticGlobals, static localsWhole programFixed at compile time
HeapDynamically allocatedUntil freedProgrammer or collector
StackActivation recordsOne activationAutomatic

The heap and stack grow towards each other from opposite ends of the free space, which lets either expand as far as the other permits without a fixed split.

Static allocation requires that every size be known at compile time and that no recursion occur. Early languages such as FORTRAN 77 used it exclusively, which is precisely why they could not support recursion: a second activation would have nowhere separate to store its locals.

2. Activation Records

An activation record, or frame, holds everything one procedure activation needs. The layout varies between machines but the components are standard.

ComponentPurpose
Returned valueWhere the result is placed
Actual parametersArguments from the caller
Control linkPointer to the caller's frame
Access linkPointer to the lexically enclosing frame
Saved machine stateReturn address and saved registers
Local dataThe procedure's own variables
TemporariesIntermediate expression values

The control link and the access link are different pointers answering different questions, and confusing them is the commonest error in this topic.

The control link points to the caller — whoever invoked this activation at run time. It is used to restore the stack on return, and the chain of control links is the dynamic call chain.

The access link points to the most recent activation of the lexically enclosing procedure — whoever contains this one in the program text. It is used to find non-local variables, and the chain of access links follows the static nesting structure.

In a language without nested procedures, such as C, no access link is needed at all, because a non-local name is necessarily global and lives in the static area.

Locals are addressed as fixed offsets from a frame pointer, which is exactly the base-plus-displacement addressing mode. That is why the offset is known at compile time even though the frame's address is not.

3. The Call Sequence

Responsibility for building and dismantling a frame is split between caller and callee, and the split is a convention rather than a necessity.

The caller typically evaluates arguments, places them in the new frame, saves any caller-saved registers, and transfers control.

The callee typically saves the return address and callee-saved registers, allocates space for locals, and sets up the frame pointer.

The return sequence reverses both halves: the callee places the return value, restores registers and the stack pointer, and jumps back; the caller then retrieves the value and restores its own saved registers.

Placing shared responsibilities in the callee saves code space, since the callee's sequence appears once while the caller's appears at every call site. This is why register saving is pushed to the callee where possible.

The register-saving split has its own logic. A caller-saved register may be destroyed by a call, so the caller preserves it only if it needs the value afterwards. A callee-saved register must be restored, so the callee preserves it only if it actually uses it. The split minimises total saves because each side skips the work it does not need.

4. Scope Rules

Two rules decide which declaration a non-local name refers to, and they can give different answers for the same program.

Static scope, also called lexical scope, resolves a name using the program text. A name refers to the declaration in the nearest enclosing block, determined at compile time.

Dynamic scope resolves a name using the call chain. A name refers to the most recent still-active declaration, determined at run time.

Consider a procedure that reads a variable it does not declare, called from two places that each declare it differently. Static scope gives the same answer in both cases, namely whatever the text encloses. Dynamic scope gives different answers, depending on which caller is active.

Almost every modern language uses static scope, because it lets a reader determine a name's meaning from the text alone, and lets the compiler resolve it to a fixed location.

Dynamic scope requires a run-time search of the call chain or an association list, which is both slower and harder to reason about.

5. Nested Procedures

When a language allows procedures to be nested, an inner procedure may reference the locals of an enclosing one, and finding them requires machinery.

The access link solves it by pointing to the most recent activation of the immediately enclosing procedure. To reach a variable declared levels out, follow access links and then apply the fixed offset.

Setting the access link correctly at a call depends on the relative nesting depths. If the callee is nested directly inside the caller, the caller's own frame is the enclosing activation. If the callee is at the same depth or shallower, the caller follows its own access links the appropriate number of times.

A display replaces the chain walk with an array. Entry of the display points to the most recent activation at nesting depth , so a variable levels out is reached by one array lookup rather than pointer dereferences.

The trade is between access cost and maintenance cost. Access links cost per access and nothing at a call; a display costs per access but must be saved and restored around each call.

6. Parameter Passing

Four mechanisms appear, and the differences show up only under aliasing or side effects.

Call by value copies the argument into the parameter. The callee cannot affect the caller's variable at all.

Call by reference passes the address, so the parameter is an alias for the caller's variable and assignments are immediately visible.

Call by value-result, also called copy-restore, copies in on entry and copies back on exit. It resembles reference for simple cases and differs when the same variable is passed twice or modified concurrently.

Call by name substitutes the argument expression textually and re-evaluates it at every use. An array subscript passed by name is recomputed each time, which is what makes Jensen's device possible and what makes the mechanism unpredictable.

The standard discriminating test is passing the same variable twice. Under reference the two parameters alias, so an assignment through one is visible through the other. Under value-result they are independent copies until exit, when the write-back order decides the result — and that order is usually unspecified.

C has only call by value. Passing a pointer gives the appearance of reference, but the pointer itself is copied, so the callee can change what it points at and not which object the caller's variable names.

7. Heap Management

The heap holds objects whose lifetime does not match any activation, and managing it is the hardest part of the runtime.

Explicit management makes the programmer responsible. Two errors follow: a dangling reference when memory is freed while a pointer to it remains, and a memory leak when memory becomes unreachable without being freed.

A dangling reference is the more dangerous, because the memory is reused and the stale pointer silently reads or writes another object's data.

Automatic management, or garbage collection, reclaims unreachable objects.

Reference counting keeps a count per object and frees it when the count reaches zero. It reclaims immediately and spreads its cost evenly, but it cannot collect cycles: two objects referring only to each other keep each other's counts positive forever.

Mark and sweep traverses from the roots, marks everything reachable, then sweeps the heap freeing the unmarked. It collects cycles correctly, because reachability from the roots is the criterion rather than incoming references. The cost is a pause proportional to the heap size and the fragmentation left by sweeping.

A copying collector divides the heap in two and copies live objects to the other half, which compacts as it collects and makes allocation a pointer bump. The cost is half the heap sitting unused.

Generational collection exploits the observation that most objects die young, collecting a small nursery frequently and the older regions rarely.

8. Worked Examples

Example 1. Distinguish the control link from the access link, and state when each is used.

The control link points to the caller's activation record — the frame of whoever invoked this procedure at run time. It exists to restore the stack on return, and following the chain of control links traces the dynamic call sequence.

The access link points to the activation record of the lexically enclosing procedure — whoever contains this one in the program text. It exists to locate non-local variables, and following the chain traces the static nesting.

They differ whenever a procedure is called from somewhere other than its immediate lexical parent.

Suppose procedure is nested inside , and calls , which calls . Then 's control link points to 's frame, because made the call. But 's access link points to 's frame, because encloses in the text.

Using the control link to find a non-local would reach 's locals, which are not what 's text refers to.

In a language without nested procedures the access link is unnecessary, because any name a procedure does not declare must be global and lives at a fixed static address. That is why C frames carry a control link and no access link.

Example 2. A procedure passes the same variable as both arguments to a routine that increments the first parameter and doubles the second. With initially, give the result under value, reference and value-result.

Call by value. Both parameters are independent copies holding 4. The routine sets the first copy to 5 and the second to 8, then both are discarded on return. The caller's is untouched, so remains 4.

Call by reference. Both parameters alias itself, so every operation acts on directly. The first statement makes equal to 5. The second statement then reads the current value of , which is now 5, and doubles it to 10. So becomes 10.

Call by value-result. Both parameters are copies on entry, both holding 4. The routine sets the first copy to 5 and the second copy to 8, operating independently exactly as in call by value. On exit both copies are written back to .

The result depends on the write-back order. Left to right gives 5 then 8, ending at 8. Right to left gives 8 then 5, ending at 5.

The three mechanisms give three different answers, and value-result gives two depending on an order most language definitions leave unspecified. This is exactly why aliasing is the standard test for distinguishing them.

Example 3. Explain the difference between static and dynamic scope with a program that distinguishes them.

Consider this structure. A global variable is set to 10. Procedure prints without declaring it. Procedure declares a local set to 20 and then calls . The main program calls .

Under static scope, prints 10. The compiler resolves 's reference to by looking at the program text: the nearest enclosing declaration of visible from is the global one. Nothing about who calls matters, and the resolution is fixed before the program runs.

Under dynamic scope, prints 20. At run time, searches the call chain for the most recent still-active declaration of . is active and has declared its own as 20, so that is what finds.

The difference is that static scope reads the program text and dynamic scope reads the call stack.

Static scope is used by almost every modern language for two reasons. A reader can determine a name's meaning by looking at the enclosing text, without knowing every possible caller. And the compiler can resolve the name to a fixed offset, so access costs nothing at run time, whereas dynamic scope requires a search.

Example 4. Procedure contains , which contains . If is executing and needs a variable declared in , how is it reached with access links, and how with a display?

With access links, each frame points to the most recent activation of its immediate lexical parent.

's access link points to 's frame. 's access link points to 's frame.

is two levels out from , so the code follows two access links and then applies the compile-time offset of the variable within 's frame.

The cost is two pointer dereferences, and in general dereferences for a variable levels out. The compiler knows at compile time, since it is the difference in nesting depths, so it emits exactly that many.

With a display, an array indexed by nesting depth holds a pointer to the most recent activation at each depth.

is at depth 1, so the code reads display entry 1 and applies the offset — one array lookup regardless of the depth difference.

The trade is where the cost falls. Access links cost nothing at a call and at each access. A display costs at each access but must be updated on entry and restored on exit of every procedure, since a new activation at depth overwrites display entry and the old value must be saved in the frame.

For deeply nested code with frequent non-local access, the display wins. For shallow nesting or infrequent access, access links are cheaper overall.

Example 5. Why can reference counting not reclaim a cycle, and how does mark and sweep succeed?

Reference counting stores, with each object, the number of pointers currently referring to it. Creating a reference increments the count; destroying one decrements it; reaching zero frees the object.

Consider two objects each holding a pointer to the other, with no external pointer to either.

Object 's count is 1, because points to it. Object 's count is 1, because points to it.

Both counts are positive, so neither is ever freed. Yet neither is reachable from any root, so the program can never access either again. The memory is lost permanently.

The failure is structural: reference counting asks "does anything point at this?", and in a cycle the answer is always yes even when nothing outside can reach it.

Mark and sweep asks a different question: "is this reachable from the roots?"

It begins at the roots — global variables, the stack, and registers — and traverses every pointer, marking each object it reaches. Anything left unmarked when the traversal finishes is unreachable.

A cycle with no external reference is never reached during the traversal, so both objects stay unmarked and both are swept. The criterion is reachability rather than incoming references, and that is exactly what fixes the cycle problem.

The costs are different too. Reference counting spreads its work evenly and reclaims immediately but cannot handle cycles and pays on every pointer assignment. Mark and sweep handles cycles and costs nothing during normal execution, but pauses the program for a time proportional to the heap size and leaves the free space fragmented.

Example 6. Why can a language using only static allocation not support recursion?

Static allocation assigns each variable a fixed address at compile time, chosen once and used for the entire execution.

A procedure's locals therefore occupy one fixed set of addresses, and there is exactly one copy of each.

Recursion requires several activations of the same procedure to be simultaneously alive, each with its own values for the locals.

If a procedure calls itself, the inner activation writes its locals to the same fixed addresses the outer activation is using. The outer activation's values are destroyed, and when control returns to it, its variables hold the inner call's values.

The return address suffers the same fate. With one fixed slot per procedure, the inner call overwrites the outer call's return address, so the outer call eventually returns to the wrong place.

A stack fixes both problems because each activation gets its own frame, allocated on entry and released on return. The last-in-first-out discipline of the stack matches the nesting discipline of calls exactly, which is why the two are inseparable.

This is precisely why early FORTRAN, which used static allocation for speed and simplicity, did not permit recursion, and why every language that does permit it uses a stack.

Summary

The runtime environment answers two questions for every name: where its storage lives and how long it lasts. Three answers give three storage areas, plus the code region.

Static allocation fixes addresses at compile time and cannot support recursion, because a second activation would overwrite the first's locals and return address.

An activation record holds the return value, parameters, control and access links, saved state, locals and temporaries. Locals are reached as fixed offsets from a frame pointer.

The control link points to the caller and restores the stack; the access link points to the lexically enclosing activation and locates non-local variables. They differ whenever a procedure is called from outside its lexical parent, and C needs no access link at all.

Placing shared responsibilities in the callee saves code space, since the callee's sequence appears once and the caller's at every call site.

Static scope resolves names from the program text and is used by almost every modern language; dynamic scope resolves from the call chain and requires a run-time search.

Access links cost per non-local access and nothing at a call; a display costs per access but must be saved and restored around every call.

Call by value copies, reference aliases, value-result copies in and out, and name re-evaluates the argument at every use. Passing the same variable twice is the standard test that distinguishes them, and value-result's answer depends on an unspecified write-back order.

Explicit heap management risks dangling references and leaks. Reference counting reclaims promptly but cannot collect cycles, because it asks whether anything points at an object rather than whether the object is reachable. Mark and sweep uses reachability and therefore collects cycles, at the cost of a pause and fragmentation.

Key formulas & results

Everything to memorise for the exam hall, in one card. Screenshot this for revision.

The organising tool
FOR EVERY NAME, ASK WHERE ITS STORAGE LIVES AND HOW LONG IT LASTS. THE THREE ANSWERS GENERATE THE THREE STORAGE AREAS.
THE STACK WORKS BECAUSE CALL NESTING IS LAST-IN-FIRST-OUT; THE HEAP NEEDS MACHINERY BECAUSE ALLOCATION LIFETIMES ARE NOT.
The four regions
CODE HOLDS INSTRUCTIONS, STATIC HOLDS GLOBALS AND STATIC LOCALS, HEAP HOLDS DYNAMIC ALLOCATIONS, AND STACK HOLDS ACTIVATION RECORDS.
HEAP AND STACK GROW TOWARDS EACH OTHER FROM OPPOSITE ENDS, SO EITHER CAN EXPAND AS FAR AS THE OTHER PERMITS WITHOUT A FIXED SPLIT.
Why static allocation blocks recursion
EACH VARIABLE HAS ONE FIXED ADDRESS, SO A SECOND ACTIVATION OVERWRITES THE FIRST'S LOCALS AND RETURN ADDRESS.
EARLY FORTRAN USED STATIC ALLOCATION AND THEREFORE COULD NOT SUPPORT RECURSION. A STACK GIVES EACH ACTIVATION ITS OWN FRAME.
Activation record contents
RETURNED VALUE, ACTUAL PARAMETERS, CONTROL LINK, ACCESS LINK, SAVED MACHINE STATE, LOCAL DATA AND TEMPORARIES.
LOCALS ARE REACHED AS FIXED COMPILE-TIME OFFSETS FROM A FRAME POINTER, WHICH IS EXACTLY THE BASE-PLUS-DISPLACEMENT ADDRESSING MODE.
Control link versus access link
THE CONTROL LINK POINTS TO THE CALLER'S FRAME AND RESTORES THE STACK. THE ACCESS LINK POINTS TO THE LEXICALLY ENCLOSING ACTIVATION AND LOCATES NON-LOCAL VARIABLES.
THEY DIFFER WHENEVER A PROCEDURE IS CALLED FROM SOMEWHERE OTHER THAN ITS IMMEDIATE LEXICAL PARENT. CONFUSING THEM IS THE COMMONEST ERROR HERE.
When no access link is needed
IN A LANGUAGE WITHOUT NESTED PROCEDURES, A NON-LOCAL NAME IS NECESSARILY GLOBAL AND LIVES AT A FIXED STATIC ADDRESS.
THAT IS WHY C FRAMES CARRY A CONTROL LINK AND NO ACCESS LINK, AND WHY NESTED-PROCEDURE LANGUAGES NEED THE EXTRA POINTER.
The call sequence split
THE CALLER EVALUATES ARGUMENTS, PLACES THEM, SAVES CALLER-SAVED REGISTERS AND TRANSFERS CONTROL. THE CALLEE SAVES THE RETURN ADDRESS AND ITS OWN REGISTERS, ALLOCATES LOCALS AND SETS THE FRAME POINTER.
PLACING SHARED RESPONSIBILITIES IN THE CALLEE SAVES CODE SPACE, SINCE THE CALLEE'S SEQUENCE APPEARS ONCE AND THE CALLER'S AT EVERY CALL SITE.
The register-saving logic
A CALLER-SAVED REGISTER MAY BE DESTROYED BY A CALL, SO THE CALLER PRESERVES IT ONLY IF NEEDED AFTERWARDS. A CALLEE-SAVED REGISTER MUST BE RESTORED, SO THE CALLEE PRESERVES IT ONLY IF IT USES IT.
THE SPLIT MINIMISES TOTAL SAVES BECAUSE EACH SIDE SKIPS THE WORK IT DOES NOT NEED.
Static versus dynamic scope
STATIC SCOPE RESOLVES A NAME USING THE PROGRAM TEXT, AT COMPILE TIME. DYNAMIC SCOPE RESOLVES IT USING THE CALL CHAIN, AT RUN TIME.
ALMOST EVERY MODERN LANGUAGE USES STATIC SCOPE, BECAUSE A READER CAN DETERMINE A NAME'S MEANING FROM THE TEXT AND THE COMPILER CAN FIX ITS LOCATION.
Reaching a non-local by access links
TO REACH A VARIABLE DECLARED k LEVELS OUT, FOLLOW k ACCESS LINKS AND THEN APPLY THE FIXED OFFSET.
THE COMPILER KNOWS k AT COMPILE TIME, SINCE IT IS THE DIFFERENCE IN NESTING DEPTHS, SO IT EMITS EXACTLY THAT MANY DEREFERENCES.
Setting the access link
IF THE CALLEE IS NESTED DIRECTLY INSIDE THE CALLER, THE CALLER'S OWN FRAME IS THE ENCLOSING ACTIVATION. OTHERWISE THE CALLER FOLLOWS ITS OWN ACCESS LINKS THE APPROPRIATE NUMBER OF TIMES.
THE NUMBER OF HOPS DEPENDS ON THE DIFFERENCE IN NESTING DEPTHS BETWEEN CALLER AND CALLEE, AND IS KNOWN AT COMPILE TIME.
Displays
ENTRY i OF THE DISPLAY POINTS TO THE MOST RECENT ACTIVATION AT NESTING DEPTH i, SO ANY NON-LOCAL IS ONE ARRAY LOOKUP AWAY.
ACCESS LINKS COST O(k) PER ACCESS AND NOTHING AT A CALL; A DISPLAY COSTS O(1) PER ACCESS BUT MUST BE SAVED AND RESTORED AROUND EVERY CALL.
The four parameter mechanisms
VALUE COPIES IN. REFERENCE PASSES AN ADDRESS AND ALIASES. VALUE-RESULT COPIES IN AND BACK OUT. NAME SUBSTITUTES THE EXPRESSION AND RE-EVALUATES IT AT EVERY USE.
AN ARRAY SUBSCRIPT PASSED BY NAME IS RECOMPUTED EACH TIME, WHICH IS WHAT MAKES JENSEN'S DEVICE POSSIBLE AND THE MECHANISM UNPREDICTABLE.
The discriminating test
PASS THE SAME VARIABLE TWICE. UNDER REFERENCE THE PARAMETERS ALIAS; UNDER VALUE-RESULT THEY ARE INDEPENDENT UNTIL EXIT, WHEN THE WRITE-BACK ORDER DECIDES.
THAT ORDER IS USUALLY UNSPECIFIED, WHICH IS WHY VALUE-RESULT IS CONSIDERED FRAGILE AND WHY ALIASING IS THE STANDARD EXAM SETUP.
C's mechanism
C HAS ONLY CALL BY VALUE. PASSING A POINTER GIVES THE APPEARANCE OF REFERENCE, BUT THE POINTER ITSELF IS COPIED.
THE CALLEE CAN CHANGE WHAT THE POINTER POINTS AT AND NOT WHICH OBJECT THE CALLER'S VARIABLE NAMES.
The two explicit-management errors
A DANGLING REFERENCE IS A POINTER TO FREED MEMORY. A MEMORY LEAK IS UNREACHABLE MEMORY THAT WAS NEVER FREED.
THE DANGLING REFERENCE IS MORE DANGEROUS, BECAUSE THE MEMORY IS REUSED AND THE STALE POINTER SILENTLY READS OR WRITES ANOTHER OBJECT'S DATA.
Reference counting
KEEP A COUNT PER OBJECT AND FREE IT WHEN THE COUNT REACHES ZERO. IT RECLAIMS IMMEDIATELY AND SPREADS ITS COST EVENLY.
IT CANNOT COLLECT CYCLES, BECAUSE TWO OBJECTS REFERRING ONLY TO EACH OTHER KEEP EACH OTHER'S COUNTS POSITIVE FOREVER.
Mark and sweep
TRAVERSE FROM THE ROOTS, MARK EVERYTHING REACHABLE, THEN SWEEP THE HEAP FREEING THE UNMARKED.
IT COLLECTS CYCLES BECAUSE THE CRITERION IS REACHABILITY RATHER THAN INCOMING REFERENCES. THE COST IS A PAUSE PROPORTIONAL TO HEAP SIZE AND FRAGMENTATION.
Copying and generational collection
A COPYING COLLECTOR DIVIDES THE HEAP AND COPIES LIVE OBJECTS ACROSS, COMPACTING AS IT GOES SO ALLOCATION IS A POINTER BUMP.
GENERATIONAL COLLECTION EXPLOITS MOST OBJECTS DYING YOUNG, COLLECTING A SMALL NURSERY OFTEN AND OLDER REGIONS RARELY.
⚠️

Traps GATE sets — and how to dodge them

These are the exact option-traps and misreads that cost marks under negative marking.

WATCH OUT
Using the control link to find a non-local variable
The control link reaches the caller, which need not be the lexically enclosing procedure. Non-locals are found through the access link, and the two coincide only when a procedure is called from its immediate lexical parent.
WATCH OUT
Assuming every language needs an access link
Only languages permitting nested procedures do. In C, any name a procedure does not declare must be global and lives at a fixed static address, so the frame carries a control link alone.
WATCH OUT
Treating a display as strictly better than access links
It makes each non-local access constant time but must be saved and restored around every call, since a new activation at a given depth overwrites that display entry. For shallow nesting or rare non-local access, access links cost less overall.
WATCH OUT
Confusing value-result with reference
They agree in simple cases and diverge under aliasing. Passing the same variable twice makes reference parameters alias immediately, while value-result keeps independent copies until exit, when an unspecified write-back order decides the outcome.
WATCH OUT
Claiming C supports call by reference
It supports only call by value. Passing an address copies the address, so the callee can modify the pointed-to object but cannot change which object the caller's variable names. That requires a pointer to pointer.
WATCH OUT
Expecting dynamic scope to be resolvable at compile time
It depends on the call chain, which is a run-time property. The compiler cannot fix a location for the name, so implementation requires a run-time search of active declarations or an association list.
WATCH OUT
Assuming reference counting handles all garbage
It cannot reclaim cycles, because it asks whether anything points at an object rather than whether the object is reachable. Two mutually referring objects keep each other alive forever with no external reference.
WATCH OUT
Assuming mark and sweep is free during execution
It costs nothing on pointer assignments but pauses the program for a time proportional to the heap size, and sweeping leaves the free space fragmented. Reference counting spreads its cost instead of concentrating it.
WATCH OUT
Treating a memory leak as more serious than a dangling reference
A leak wastes memory but the program remains correct. A dangling reference reads or writes memory that has been reused for something else, which corrupts unrelated data silently and produces bugs that appear far from their cause.
WATCH OUT
Believing static allocation is merely slower for recursion
It makes recursion impossible, not slow. One fixed address per local means the second activation destroys the first's values and return address, so the outer call returns to the wrong place.
WATCH OUT
Placing caller responsibilities in the callee arbitrarily
The division is a convention, but the reasoning is code size: the callee's sequence appears once while the caller's appears at every call site, so shared work belongs in the callee where possible.
WATCH OUT
Assuming call by name is just lazy evaluation
It re-evaluates the argument expression at every use rather than once on demand. An array subscript passed by name is recomputed each time, which produces different results when the subscript variable changes between uses.

Exam-pattern practice

PYQ-style questions with full solutions. Work through them as a readiness check — mark yourself honestly and get your gap report at the end.

Readiness check

Are you exam-ready for Runtime Environments?

9 problems from this chapter. Try each one, reveal the worked solution, mark yourself honestly — get your gap report at the end.

9 questions~6 min

5-minute revision

The whole chapter, distilled. Read this the night before the exam.

  • Ask where storage lives and how long it lasts.
  • Four regions: code, static, heap, stack.
  • Heap and stack grow towards each other.
  • Static allocation cannot support recursion.
  • A frame holds links, parameters, locals and temporaries.
  • The control link points to the caller.
  • The access link points to the lexical parent.
  • They differ when the caller is not the lexical parent.
  • C needs no access link.
  • Locals are offsets from a frame pointer.
  • Shared call-sequence work belongs in the callee.
  • Caller-saved registers are preserved only if needed after.
  • Callee-saved registers are preserved only if used.
  • Static scope reads the program text.
  • Dynamic scope reads the call chain.
  • Almost every modern language uses static scope.
  • Reaching k levels out costs k access-link hops.
  • A display gives constant-time non-local access.
  • A display must be saved and restored per call.
  • Value copies, reference aliases, value-result copies both ways.
  • Call by name re-evaluates at every use.
  • Pass the same variable twice to distinguish mechanisms.
  • Value-result's write-back order is usually unspecified.
  • C has only call by value.
  • A dangling reference points to freed memory.
  • A leak is unreachable memory never freed.
  • Dangling references are more dangerous than leaks.
  • Reference counting cannot collect cycles.
  • Mark and sweep uses reachability from the roots.
  • Mark and sweep pauses and fragments.
  • Copying collectors compact and waste half the heap.
  • Generational collection exploits objects dying young.

GATE question blueprint

How this topic is asked, tier by tier — so you can prep to the pattern.

Typical weightage: Compiler Design contributes roughly 6-8 of the 72 core-CS marks; runtime environments supply 1-2 of those

Question styleMarks eachTypical countWhat it tests
Activation records2~1Distinguishing control and access links and tracing both chains
Storage allocation1~1Which region holds which lifetime and why static allocation blocks recursion
Scope resolution2~1Resolving a name under static and dynamic rules for the same program
Nested procedures2~1Setting access links and comparing chain walks with a display
Parameter passing2~1Outcomes under all four mechanisms, especially with aliased arguments
Garbage collection2~1Cycle handling, pause behaviour and the reachability criterion

Exam-hall strategy

Battle-tested tips from mentors and toppers for this topic under the sectional clock.

  1. For link questions, ask whether the answer concerns returning or name resolution.
  2. Draw the lexical nesting separately from the call chain before answering.
  3. For scope questions, resolve statically from the text and dynamically from the stack.
  4. For parameter passing, check whether the same variable is passed twice.
  5. For value-result, state that the answer depends on the write-back order.
  6. For garbage collection, ask whether the criterion is references or reachability.
  7. Access-link hop counts are commonly set as NAT, which carries no negative marking, so never leave one blank.
  8. For 1-mark and 2-mark MCQs, negative marking is -1/3 and -2/3, so guess only after eliminating an option.
  9. GATE gives a single freely-navigable 180-minute window, so flag a long nesting trace and return to it.

Beyond the exam

Where this skill shows up in the job you're competing for — and in life.

Reading a stack trace

The chain of control links is exactly what a debugger walks to print the sequence of calls that led to a fault.

Diagnosing a use-after-free

Recognising that a dangling reference reads memory reused for something else explains why such bugs appear far from their cause and vary between runs.

Breaking a reference cycle

Marking one edge of a parent-child relationship as a weak reference is the standard fix in reference-counted systems, and it exists because counting cannot detect cycles.

Choosing between a struct and a pointer parameter

Knowing that C copies everything by value is what determines whether a large structure should be passed directly or by address.

Where else this topic is tested

Prepare once, score in every exam that asks it.

GATE DALow overlap — compiler design is not a component of that paper
UGC NET Computer ScienceHigh overlap — activation records, scope rules and parameter passing are examined as direct recall
ISRO / BARC / DRDO computer science papersHigh overlap — parameter-passing outcomes, static versus dynamic scope and garbage collection are recurring MCQ topics

Questions aspirants ask

Pulled from the Q&A community and mentor sessions.

Because they answer two unrelated questions, and the answers differ whenever a procedure is called from outside its lexical parent. The control link records who invoked this activation at run time, and its purpose is to restore the stack when the procedure returns. Following the chain of control links traces the dynamic call sequence, which is what a debugger prints as a stack trace. The access link records which activation lexically encloses this one in the program text, and its purpose is to locate non-local variables. Following that chain traces the static nesting structure. Consider a procedure Q nested inside P, where P calls R and R then calls Q. Q's control link points to R, because R made the call. Q's access link points to P, because P is where Q is written and therefore where Q's non-local names are declared. If the compiler used the control link to find a variable declared in P, it would reach R's frame instead, where that variable does not exist and where some unrelated local sits at the same offset. The read would return garbage silently. The two coincide only when a procedure is called directly by its lexical parent, which is common enough that the distinction is easy to overlook and precisely why it is examined. In a language without nested procedures, such as C, the access link disappears entirely, because any name a procedure does not declare must be global and lives at a fixed static address requiring no chain walk at all.

When nesting is deep and non-local access is frequent, because the display converts an O(k) chain walk into a single array lookup. With access links, reaching a variable declared k levels out requires following k pointers, and although k is known at compile time so the code is straight-line, the dereferences are real memory accesses that may miss the cache. With a display, entry i holds a pointer to the most recent activation at nesting depth i, so any non-local is reached by indexing the array once and applying the compile-time offset. The cost moves to procedure entry and exit. When a procedure at depth d begins, it overwrites display entry d with a pointer to its own frame, and the previous value must be saved in the frame so it can be restored on return. Every call therefore pays a save and a restore whether or not any non-local access occurs. The comparison is straightforward. If procedures are shallowly nested, k is small and the chain walk is cheap, so the display's per-call overhead is not repaid. If procedures are deeply nested but rarely reference distant non-locals, the same conclusion holds. The display wins when deep nesting combines with frequent access, which was common in Algol-family languages designed around nested procedures and is rarer in modern code. In practice most contemporary languages either forbid nested procedures, making the whole question moot, or implement closures by capturing an environment on the heap, which sidesteps both mechanisms.

Because it lets each side skip work it does not need, and the alternative conventions both waste effort. Suppose all registers were caller-saved. Then before every call the caller would preserve every register it might care about, without knowing whether the callee actually uses them. A callee touching two registers would still cost the caller a full save and restore of the register file. Suppose instead all registers were callee-saved. Then every procedure would preserve every register it touches, without knowing whether the caller had anything live in them. A leaf procedure using several registers would save them all even when the caller had nothing to protect. The split resolves both wastes. A caller-saved register is one the callee may destroy freely, so the caller preserves it only when it holds a value needed after the call — which the caller knows and the callee cannot. A callee-saved register must be restored, so the callee preserves it only when it actually uses it — which the callee knows and the caller cannot. Each side has exactly the information needed for its half of the decision. Compilers exploit this by allocating values with short lifetimes, not spanning any call, into caller-saved registers, and values living across calls into callee-saved ones. Register allocation therefore interacts directly with the calling convention, which is one reason the convention is fixed by the platform's application binary interface rather than chosen per compiler.

For two reasons, one about readers and one about compilers. For readers, static scope means a name's meaning is determined by the enclosing program text and nothing else. Looking at a procedure, you can say what every identifier in it refers to by examining the code around it. Under dynamic scope you cannot, because the answer depends on who is calling, so understanding a procedure requires enumerating every possible caller and every caller of those. A procedure's behaviour becomes non-local in a way that defeats modular reasoning, and a change in one part of the program can silently alter another part that never mentioned it. For compilers, static scope means every name can be resolved to a fixed storage location before the program runs — a static address for a global, or a frame offset plus a known number of access-link hops for a non-local. The generated code is a direct memory reference and costs nothing extra. Dynamic scope requires a run-time search of the active declarations, either by walking the call chain or by maintaining an association list per name, and both cost time on every access and space to maintain. Dynamic scope survives in a few places where its non-locality is the point, such as exception handlers, special variables in some Lisps, and shell environment variables, where a caller deliberately configures behaviour for everything it invokes. Even there, modern designs usually confine it to explicitly marked variables rather than making it the default.

Because the information it maintains is fundamentally the wrong information, not merely incomplete. A reference count answers the question: how many pointers currently refer to this object? Collection requires answering a different question: is this object reachable from the roots? Those coincide for acyclic structures but diverge for cycles, where every object in the cycle has a positive count supplied entirely by other members of the cycle, none of which is reachable from outside. No amount of extra counting fixes this, because the counts are locally correct. Each object genuinely does have a pointer to it. The problem is that local information cannot detect a globally unreachable region, and detecting it requires a traversal from the roots — which is precisely what mark-and-sweep does and what reference counting was designed to avoid. Practical systems work around it in three ways. Some add a cycle detector that periodically traces suspicious subgraphs, which is a partial mark-and-sweep and reintroduces its costs. Some provide weak references that do not contribute to the count, and require the programmer to break cycles manually by making one edge weak — the common approach in reference-counted systems, and a genuine burden on the programmer. Some abandon reference counting for tracing collection entirely, which is what most managed runtimes chose. The trade being made is latency against completeness. Reference counting reclaims promptly and predictably but cannot be complete; tracing is complete but pauses. Generational collectors reduce the pauses enough that most systems now accept them.
Header Logo