Introduction
Two questions decide what a procedure call does with names. Parameter passing answers how an actual parameter in the call is connected to the formal parameter in the procedure: by copying its value, by sharing its location, by copying in and out, or by re-evaluating the argument text. Scope rules answer which declaration a free name in a procedure body refers to: the one that encloses it in the program text (static scope) or the most recent one in the chain of calls (dynamic scope).
Both are favourites for output-prediction questions: a short program, a stated mechanism or scope rule, and the question "what is printed?". The answer follows mechanically once each rule is applied exactly.
Prerequisites
- Runtime Environments and Activation Records: activation records, control links (the call chain) and access links (the lexical chain).
- l-values (locations) and r-values (contents) of expressions, as in assignment (see the note Functions and Parameter Passing).
Core concepts
- Formal and actual parameters. Formals are the names in the procedure declaration; actuals are the expressions in the call.
- l-value and r-value.
A[i]has an l-value (the address of the element) and an r-value (the number stored there). Mechanisms differ in which of these they pass and when they compute it. - Environment and state. The environment maps names to locations; the state maps locations to values. Scope rules fix the environment; assignments change the state.
- Aliasing. Two names denote the same location. Call by reference creates aliases (a formal and a global, or two formals), which changes results.
- Static (lexical) scope. A name refers to the declaration in the nearest enclosing block or procedure in the program text. Resolved at compile time; implemented with access links or displays.
- Dynamic scope. A name refers to the declaration in the most recently called, still active, procedure that declares it. Resolved at run time by searching down the call stack.
How it works
The four mechanisms
| Mechanism | What is passed | Effect of assigning to the formal |
|---|---|---|
| Call by value | r-value of the actual, copied into a new local | Only the local copy changes |
| Call by reference | l-value of the actual (its address) | The actual's location changes immediately |
| Call by value-result (copy-restore) | r-value copied in; the actual's l-value is noted at the call | Local copy changes; it is copied back to the noted location on return |
| Call by name | The argument text, re-evaluated in the caller's environment at each use (a thunk) | The location named by the text at that moment changes |
If the actual is not an l-value, such as a + 1, call by reference typically passes the address of a temporary that holds its value. Call by value-result differs from call by reference only when the procedure can reach the same location by another path (an alias) during the call. Call by name differs from call by reference when the argument's location depends on something the procedure changes.
Example 1: one variable, four outcomes
int a = 1;
void f(int x) { x = x + 1; a = a + 10; x = x + 100; }
main: f(a); then a is ?| Mechanism | Trace | Final a |
|---|---|---|
| Value | x: 1, 2, 102; a: 11 | 11 |
| Reference | x and a are one location: 2, 12, 112 | 112 |
| Value-result | x: 1, 2, 102 while a becomes 11; on return a = x | 102 |
| Name | every use of x means a: 2, 12, 112 | 112 |
Value-result and reference disagree because a is changed through the global name while the copy x is separate.
Example 2: when the argument's location moves
int i = 0; int A[2] = {5, 7};
void q(int x) { i = 1; x = x + 1; }
main: q(A[i]);- Value:
Ais unchanged,{5, 7}. - Reference: the address of
A[0]is fixed at the call;Abecomes{6, 7}. - Value-result:
xstarts as 5 and becomes 6; it is copied back toA[0], the location noted at the call:{6, 7}. - Name:
x = x + 1re-evaluatesA[i]with on both sides:A[1] = A[1] + 1, soAbecomes{5, 8}.
Some descriptions of value-result recompute the destination address on return; under that variant the copy-back goes to A[1]. Questions that rely on the difference say which rule applies.
Example 3: swap fails under call by name
void swap(int x, int y) { int t = x; x = y; y = t; }
int i = 0; int A[2] = {1, 5};
main: swap(i, A[i]);By reference, x is i and y is A[0]: , , , giving and A = {0, 5}, a correct swap. By name, each use re-evaluates: ; ; then y is A[i] with the new , so . The result is and A = {1, 0}: A[0] never changed. By value nothing outside changes.
Example 4: aliasing between formals
int a = 2;
void h(int x, int y) { x = x + 1; y = y + y; }
main: h(a, a);By value, a stays 2. By reference both formals are a: , then . By value-result becomes 3 and becomes 4 independently; copying back left to right leaves (the last copy wins, so the order of copy-back matters).
Example 5: all four mechanisms on one call
int i = 1; int A[5] = {10, 20, 30, 40, 50};
void p(int x, int y) { y = y + 1; x = x + 2; i = i + 1; y = y + x; }
main: p(i, A[i]);| Mechanism | Trace | Final i | Final A |
|---|---|---|---|
| Value | copies x = 1, y = 20; only i = i + 1 reaches a global | 2 | unchanged |
| Reference | x is i, y is A[1]: A[1] = 21; i = 3; i = 4; A[1] = 21 + 4 = 25 | 4 | A[1] = 25 |
| Value-result | copies x = 1, y = 20: y = 21, x = 3, global i = 2, y = 24; copy back i = 3, A[1] = 24 | 3 | A[1] = 24 |
| Name | A[1] = 21 (i is 1); i = 3; i = 4; then y = y + x is A[4] = A[4] + i = 54 | 4 | A[1] = 21, A[4] = 54 |
Under value-result the copy of x overwrites the increment that i = i + 1 made through the global name, so ends at 3, not 4. Under name, the last statement touches A[4], an element the call never mentioned: the index is re-evaluated after has changed.
Static versus dynamic scope
Under static scope, a free name in a procedure is bound to the declaration that encloses the procedure's text. For C-like programs with all procedures at the outermost level, that means: own locals, then globals. Under dynamic scope, the binding is the most recent active declaration: own locals, then the caller's, then the caller's caller's, and so on down the stack, then globals.
Example 6: the basic contrast.
int x = 1;
void f() { print(x); }
void g() { int x = 2; f(); }
main: g(); f();Static: f always sees the global x, printing 1 1. Dynamic: when f is called from g, the most recent x is g's, so the first call prints 2; the second call is from main and prints 1. Output 2 1.

Example 7: assignments as well as reads.
int n = 1;
void show() { print(n); }
void inc() { n = n + 1; }
void two() { int n = 5; inc(); show(); }
main: two(); show();Static: inc changes the global n to 2 and both calls of show print the global: 2 2. Dynamic: inside two, the n that inc and show see is two's local, so inc makes it 6 and show prints 6; after two returns, show sees the global, still 1. Output 6 1.
Implementation. Static scope uses the access links or display of the runtime-environments note; the hop count is fixed at compile time. Dynamic scope can be implemented by deep access (search the control-link chain for the first record that declares the name) or shallow access (keep one current slot per name, saving the old value on entry to a procedure that declares it and restoring it on exit).
Static scope with nested procedures
With nested declarations, static scope looks outward through the enclosing procedures' texts, not through the callers. If p is declared inside main and q is declared inside p, a name used in q and declared in both main and the caller of q resolves to p's or main's declaration, whichever encloses q first, no matter who called q. Under dynamic scope the caller's declaration wins.
Example 8: nested procedures.
int x = 10;
void p() {
int x = 20;
void q() { print(x); }
void r() { int x = 30; q(); }
r(); q();
}
main: p();- Static:
qis declared insidep, so its freexisp's, whoever calls it. Both calls print 20: output20 20. - Dynamic: the first call of
qcomes fromr, whosexis the most recent: 30. The second comes fromp: 20. Output30 20. - If
qwere declared at the outer level instead (next top), static scope would print the global:10 10. Dynamic scope would still print30 20, because the call history is unchanged.
Important properties and observations
- Reference and name agree on a simple variable argument whose location cannot change (Example 1); they differ for
A[i]whenichanges (Examples 2 and 3). - Value-result and reference agree unless aliasing exists during the call (Examples 1 and 4).
- Call by value never changes the actual; but passing a pointer by value still lets the callee change the pointed-to object, which is how C simulates reference parameters.
- Call by name is lazy: an argument never used is never evaluated, and one used twice is evaluated twice.
- Static scope is decided by program text; dynamic scope by the call history. The same program can print different results under the two rules only if some procedure has a free name declared in more than one place.
- C, C++, Java and Pascal use static scope; C passes everything by value (arrays decay to a pointer passed by value); C++ adds reference parameters.
Common misconceptions.
- "Value-result is the same as reference." Not when the procedure also reaches the actual through another name.
- "Dynamic scope means the variable is allocated dynamically." It is about name resolution, not storage.
- "Under static scope the callee sees the caller's locals." Only if the callee is textually nested in the caller.
GATE examination perspective
- Output under a stated mechanism. Trace each assignment, keeping the formal's location (reference), a separate copy (value, value-result) or the argument text (name). For value-result, apply the copy-back order the question implies.
- Output under static and dynamic scope. For each free name, find the enclosing declaration (static) or the most recent active one on the call chain (dynamic). Assignments to a free name change whichever variable that rule picks.
- Aliasing traps. The same variable passed twice, or a global also passed as an argument.
- Array arguments with changing indices. Separate name from reference.
- Value-result copy-back can undo a global update made during the call. (Example 5: ends at 3, not 4).
- Nested procedures under static scope. Find where the procedure is declared, not where it is called (Example 8).
- Which needs what at run time. Static scope uses access links or displays; dynamic scope searches the control chain.
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 1.6.1 (the static/dynamic distinction), Section 1.6.2 (environments and states), Section 1.6.3 (static scope and block structure), Section 1.6.5 (dynamic scope), Section 1.6.6 (parameter-passing mechanisms: call by value, call by reference, call by name), Section 1.6.7 (aliasing), Section 7.3 (access links and displays for static scope).