G27
Self-checkedTier 1 13 min readcd.runtime-parameter-passing

Parameter passing modes and access links

How actual parameters reach a procedure: call by value, call by reference, copy-restore (value-result) and call by name with thunks, what each does to aliasing and to the caller's variables, Jensen's device, and how nested procedures reach non-local data through access links or a display.

Why it exists

When procedure p(x, y) is called as p(a, b), the meaning of the link between the formal parameters x, y and the actual parameters a, b is not fixed by the idea of a procedure. Whether p can change the caller's a, whether x and y can be the same variable, how often an actual parameter expression is evaluated: all of this depends on the parameter-passing mode, and the same program prints different results in different modes. This is a classical GATE topic, usually asked as "what is printed".

Prerequisites

You need the run-time stack and activation records (activation records). Two words: an l-value of an expression is the location it denotes (i, a[2]), an r-value is its value (3).

Precise definitions

The actual parameters are the expressions in the call; the formal parameters are the names in the procedure header.

Call by value. The caller evaluates each actual parameter to an r-value and the callee receives a copy in its own activation record. Assignments to the formal parameter change only the copy; the caller's variable is unchanged. (C passes everything by value; passing a pointer by value lets the callee change what the pointer points to.) Cheap and safe for small values, costly for big objects.

Call by reference. The caller passes the address (l-value) of the actual parameter; the formal parameter becomes an alias of it. Every read or write of the formal is a read or write of the caller's variable, immediately. If the actual parameter is an expression with no l-value (such as x + 1) the compiler evaluates it into a temporary and passes the address of that. The location of a[i] is computed once, at the call, using the value of i at that time. Two formals can be aliases of the same variable (p(a, a)). Fortran passes by reference; C++ references and C pointers give the same effect.

Copy-restore (value-result, copy-in copy-out). The value is copied in at the call like call by value, the callee works on its local copy, and when the procedure returns the final value of the formal is copied back into the actual parameter's location (computed at the time of the call). Unlike call by reference, the caller does not see changes until the return, and aliasing is not seen inside the callee. If two formals are bound to the same actual variable, the result depends on the order in which they are copied back; the usual order is left to right, so the last copy-back wins.

Call by name. The actual parameter is substituted textually for every occurrence of the formal in the body (with the free variables of the actual interpreted in the caller's environment, i.e. renaming conflicting names). Implementation: the compiler passes a thunk, a small procedure (with the caller's environment) that re-evaluates the actual parameter every time the formal is used. Reading x evaluates the thunk; assigning to x assigns to whatever l-value the thunk denotes at that moment, which can be a different location from the one at the call if a variable in the expression has changed. Call by name was used in Algol 60; its descendant, lazy evaluation with sharing (call by need), is used in Haskell.

Summary.

ModePassesCallee's changes seen by the callerThe actual is evaluated
valuecopy of the r-valueneveronce, at the call
referenceaddress (l-value)at once (alias)its address once, at the call
copy-restorecopy in, copy outat the returnits value and address once, at the call
namethunkat once (the thunk is an l-value at each use)at every use

Access links and the display (static scoping). A nested procedure reaches the variables of the procedures that enclose it by the access links of the activation records, one hop per level of nesting: the lookup of a variable declared kk levels out follows kk links. A display keeps, in an array indexed by nesting depth, a pointer to the latest record of each depth, so the record of any enclosing level is found in constant time; the price is that a call and a return must update the display. A procedure parameter that is itself a procedure needs, besides the code address, the access link of its definition (a closure). Details and the figures are on the activation records page.

How it works

One program, four modes. Main has i = 1 and the array a = [5, 2, 7]; the call is swap(i, a[i]) where swap(x, y) does t = x; x = y; y = t. The effect depends on the mode.

By value nothing changes: swap exchanges its own copies.

swap(i, a[i]) with parameters passed by value: start

Program executed, not typed

parameters passed by value

1var i = 1;
2var a = [5, 2, 7];
3proc swap(x, y) {
4 var t = 0;
5 t = x;
6 x = y;
7 y = t;
8}
9swap(i, a[i]);
10print i;
11print a[0];
12print a[1];
13print a[2];
control stack (top first)
#0 main (outermost)
i = 1
a = [5, 2, 7]
output (nothing yet)

Step 0

Main has i = 1 and the array a = [5, 2, 7]. The call swap(i, a[1]) should exchange i and a[1], but what really happens depends on how the parameters are passed.

0 / 10
computed by script: the program is executed step by step on a stack of activation records. python3 scripts/gen-cd-run-traces.py

By reference x is an alias of i and y of a[1] (the location was computed at the call, when i was 1). The swap works: i becomes 2 and a[1] becomes 1.

swap(i, a[i]) with parameters passed by ref: start

Program executed, not typed

parameters passed by ref

1var i = 1;
2var a = [5, 2, 7];
3proc swap(ref x, ref y) {
4 var t = 0;
5 t = x;
6 x = y;
7 y = t;
8}
9swap(i, a[i]);
10print i;
11print a[0];
12print a[1];
13print a[2];
control stack (top first)
#0 main (outermost)
i = 1
a = [5, 2, 7]
output (nothing yet)

Step 0

Main has i = 1 and the array a = [5, 2, 7]. The call swap(i, a[1]) should exchange i and a[1], but what really happens depends on how the parameters are passed.

0 / 10
computed by script: the program is executed step by step on a stack of activation records. python3 scripts/gen-cd-run-traces.py

By copy-restore the swap is done on local copies and copied back at the return: the same final result as by reference here, but the changes become visible only at the return.

swap(i, a[i]) with parameters passed by copy-restore: start

Program executed, not typed

parameters passed by copy-restore

1var i = 1;
2var a = [5, 2, 7];
3proc swap(copy-restore x, copy-restore y) {
4 var t = 0;
5 t = x;
6 x = y;
7 y = t;
8}
9swap(i, a[i]);
10print i;
11print a[0];
12print a[1];
13print a[2];
control stack (top first)
#0 main (outermost)
i = 1
a = [5, 2, 7]
output (nothing yet)

Step 0

Main has i = 1 and the array a = [5, 2, 7]. The call swap(i, a[1]) should exchange i and a[1], but what really happens depends on how the parameters are passed.

0 / 10
computed by script: the program is executed step by step on a stack of activation records. python3 scripts/gen-cd-run-traces.py

By name the thunk of y is the expression a[i], re-evaluated at every use. After x = y has changed i to 2, the assignment y = t stores into a[2], not into a[1]: the swap fails in a surprising way (i = 2, a = [5, 2, 1]).

swap(i, a[i]) with parameters passed by name: start

Program executed, not typed

parameters passed by name

1var i = 1;
2var a = [5, 2, 7];
3proc swap(name x, name y) {
4 var t = 0;
5 t = x;
6 x = y;
7 y = t;
8}
9swap(i, a[i]);
10print i;
11print a[0];
12print a[1];
13print a[2];
control stack (top first)
#0 main (outermost)
i = 1
a = [5, 2, 7]
output (nothing yet)

Step 0

Main has i = 1 and the array a = [5, 2, 7]. The call swap(i, a[1]) should exchange i and a[1], but what really happens depends on how the parameters are passed.

0 / 10
computed by script: the program is executed step by step on a stack of activation records. python3 scripts/gen-cd-run-traces.py

Next, aliasing: p(a, a) with a = 3, where p(x, y) does x = x + 1; y = y * 2; print x + y and the caller prints a afterwards. By reference x and y are the same variable: after the two assignments a = 8, x + y = 16.

p(a, a) with parameters passed by ref: start

Program executed, not typed

parameters passed by ref

1var a = 3;
2proc p(ref x, ref y) {
3 x = x + 1;
4 y = y * 2;
5 print x + y;
6}
7p(a, a);
8print a;
control stack (top first)
#0 main (outermost)
a = 3
output (nothing yet)

Step 0

main has a = 3 and calls p(a, a): both parameters are the same variable. p adds one to x, doubles y and prints x + y.

0 / 7
computed by script: the program is executed step by step on a stack of activation records. python3 scripts/gen-cd-run-traces.py

By copy-restore x and y are separate local copies: x = 4, y = 6, x + y = 10, and at the return a receives x and then y, so a ends as 6 (the last copy-back wins).

p(a, a) with parameters passed by copy-restore: start

Program executed, not typed

parameters passed by copy-restore

1var a = 3;
2proc p(copy-restore x, copy-restore y) {
3 x = x + 1;
4 y = y * 2;
5 print x + y;
6}
7p(a, a);
8print a;
control stack (top first)
#0 main (outermost)
a = 3
output (nothing yet)

Step 0

main has a = 3 and calls p(a, a): both parameters are the same variable. p adds one to x, doubles y and prints x + y.

0 / 7
computed by script: the program is executed step by step on a stack of activation records. python3 scripts/gen-cd-run-traces.py

Finally Jensen's device, the standard use of call by name. sum(k, e, n) assigns 1 to k and adds the value of e for each k up to n. Called as sum(i, i * i, 3), the formal k stands for the variable i and the formal e for the expression i * i, re-evaluated each time with the current i: the loop computes 1+4+9=141 + 4 + 9 = 14 and leaves i = 4. With any other mode this would not work, since e would be computed once.

Call by name: Jensen's device: start

Program executed, not typed

parameters k and e passed by name, n by value

1var i = 0;
2var s = 0;
3proc sum(name k, name e, n) {
4 s = 0;
5 k = 1;
6 while (k < (n + 1)) {
7 s = s + e;
8 k = k + 1;
9 }
10}
11sum(i, i * i, 3);
12print s;
13print i;
control stack (top first)
#0 main (outermost)
i = 0
s = 0
output (nothing yet)

Step 0

sum(k, e, n) adds the value of the expression e for k = 1, 2, ..., n. Called as sum(i, i * i, 3), the thunk of k is the variable i and the thunk of e is the expression i * i, which is re-evaluated at every use with the current i.

0 / 17
computed by script: the program is executed step by step on a stack of activation records. python3 scripts/gen-cd-run-traces.py

The same effect as call by reference can be had in C with pointers. The program below shows by value (nothing changes) and by pointer (the swap works, with the address &a[i] computed at the call while i is still 1):

#include <stdio.h>
 
static void swap_value(int x, int y) {
    int t = x;
    x = y;
    y = t;
}
 
static void swap_pointer(int *x, int *y) {
    int t = *x;
    *x = *y;
    *y = t;
}
 
static void show(const char *how, int i, const int a[3]) {
    printf("%-9s i=%d a=[%d, %d, %d]\n", how, i, a[0], a[1], a[2]);
}
 
int main(void) {
    int i = 1;
    int a[3] = {5, 2, 7};
    swap_value(i, a[i]);
    show("value", i, a);
    swap_pointer(&i, &a[i]); /* &a[i] is computed once, at the call, while i is still 1 */
    show("pointers", i, a);
    return 0;
}
value     i=1 a=[5, 2, 7]
pointers  i=2 a=[5, 1, 7]

Worked examples

EX 1Four modes, one program

Easy

Using the animations: for swap(i, a[i]) with i = 1, a = [5, 2, 7] and then printing i, a[0], a[1], a[2]: value: 1 5 2 7; reference: 2 5 1 7; copy-restore: 2 5 1 7; name: 2 5 2 1. All four were produced by the interpreter and recomputed by an independent translation. To solve such a question by hand, apply each mode's rule to t = x; x = y; y = t carefully, recomputing a[i] for name.

EX 2By value, a function cannot change the caller

Easy

void inc(int n) { n = n + 1; } called as inc(v) with v = 5: v stays 5 under call by value. To change v the caller passes &v and the function writes *n = *n + 1, which is call by value of the address (and is how C simulates call by reference). Arrays passed to functions in C decay to a pointer to the first element, so the function can change the caller's array elements, although everything is passed by value.

EX 3Aliasing: reference versus copy-restore

Medium

p(a, a) with a = 3, p(x, y): x = x + 1; y = y * 2; print x + y, then print a. Reference: x and y are a: a = 4, then a = 8; x + y = 8 + 8 = 16; final a = 8. Copy-restore: x = 4, y = 6; x + y = 10; at the return, a = x = 4 and then a = y = 6: final a = 6. Value: x = 4, y = 6, prints 10, a stays 3. Name: both thunks are a: same as reference: prints 16, a = 8. If the copy-back were right to left the final a would be 4: that is why the order must be specified.

EX 4Jensen's device

Medium

sum(k, e, n): s = 0; k = 1; while (k < n + 1) { s = s + e; k = k + 1; } called as sum(i, i * i, 3) with the formals k and e by name and n by value. The thunk of k is the l-value i: k = 1 assigns i = 1, k = k + 1 increments i. The thunk of e is i * i evaluated with the current i: at the three uses it gives 1, 4, 9. So s = 14 and, when the loop ends, i = 4. If e were passed by value or by reference, the expression i * i would be evaluated once, at the call, when i is 0: every addition would add 0 and s would be 0 (with k by value the output would be 0 0; with k by reference, s = 0 and i = 4). The essential part is call by name for e; with k by reference and e by name the result is also 14 (all of these were run).

EX 5Which statements are true?

GATE level

(a) "Call by value cannot be used to modify the caller's variables": true (apart from through pointers passed by value). (b) "With call by reference two different formal parameters may refer to the same variable": true. (c) "In call by name the actual parameter is evaluated exactly once": false; at each use of the formal. (d) "Copy-restore and call by reference give the same results for every program": false; with aliased arguments or when the callee modifies a non-local variable that is also an actual parameter they differ, and the callee sees different intermediate values. (e) "Call by name can be implemented with thunks": true. (f) "A display gives constant-time access to the non-local variables of enclosing procedures": true. (g) "In call by value the callee's formal parameter is allocated in its activation record": true.

Cheat box

Cheat box

  • Value: copy of the r-value; caller never changes. Reference: address; alias; changes at once. Copy-restore: copy in, copy out at the return (order matters when aliased). Name: re-evaluate the actual at every use (thunk).
  • Reference computes the address of a[i] once, at the call; name recomputes a[i] at every use with the current i.
  • With aliasing: reference and name differ from value and copy-restore. Copy-restore: last copy-back wins (left to right).
  • C is always by value (pointers simulate reference). Fortran: by reference. Algol 60: by name.
  • Thunk = procedure that computes the actual in the caller's environment.
  • Access links or a display find non-local variables of nested procedures.

Traps and GATE patterns

Connections

The records into which parameters are placed are described in activation records. In C the corresponding facts are in functions and parameter passing and pointers. The compile-time side of the same question (type checking of arguments, evaluation order) belongs to semantic analysis (phases of a compiler).

PYQs

No PYQs attached to this concept yet

Previous year questions are being added section by section from your PYQ book, each with my own re-checked answer. Nothing is shown from memory.

Self-check

Question 1

In the swap(i, a[i]) example, which two modes give i = 2 and a = [5, 1, 7]?

Show answer

Call by reference and copy-restore.

Question 2

Why does call by name fail to swap i and a[i] correctly?

Show answer

After the first assignment changes i, the thunk a[i] denotes a different element, so the last assignment writes into the wrong place.

Question 3

For p(a, a) under copy-restore, what decides the final value of a?

Show answer

The order of copying back: the formal that is copied last wins (left to right: the second parameter).

Question 4

Which mode makes Jensen's device work, and why?

Show answer

Call by name (for the expression e; the loop variable may be passed by name or by reference): the expression is passed as a thunk that is re-evaluated at each use with the current value of the loop variable.

Revision card

One-screen revision

  • Value (copy), reference (address, alias), copy-restore (copy in, out at return), name (thunk, re-evaluated).
  • Reference: a[i] address fixed at the call; name: recomputed at every use.
  • Aliasing separates reference/name from value/copy-restore; copy-back order matters.
  • Access links or a display for non-local variables.

References

  • ·Aho, Lam, Sethi, Ullman, Compilers: Principles, Techniques, and Tools (2nd edition), Chapter 1 Introduction (1.6.6 parameter passing mechanisms) and Chapter 7 Run-Time Environments (7.3 access to nonlocal data on the stack)
  • ·Cooper and Torczon, Engineering a Compiler, Chapter 6 The Procedure Abstraction (parameter binding)

Verification

All outputs of the swap, aliasing and Jensen's-device programs come from a small interpreter that implements the four modes (scripts/cd_run.py) and are recomputed by an independent translation of each program to Python (value as plain arguments, reference as accessors fixed at the call, name as accessors re-evaluated at each use, copy-restore as returned values copied back), also on 3000 random programs with mixed modes (scripts/enum-checks/cd_run.py). The C program with pointers is compiled and run by npm run verify:c. The textbook definition of copy-restore leaves the order of copying back unspecified; this page copies back in left-to-right parameter order and says so, because the answer for aliased arguments depends on it. SECOND PASS (2026-11-02, done by the assistant, not a human check): every Self-check question was solved before the printed answer was read and all agreed; the definitions, worked examples, cheat box and traps were read claim by claim and the numbers re-derived by hand or by a small script (token counts, FIRST and FOLLOW sets, LL(1) tables and moves, LR item sets, state counts and parses, operator-precedence values, attribute evaluation and printing schemes, activation-record counts and access links, parameter-passing results, three-address code, jumping and backpatched code, leaders and blocks, value numbering and DAGs, gen/kill and data flow sets, Sethi-Ullman labels and live ranges); long C programs were not re-read line by line (they are compiled and compared by npm run verify:c, and their printed outputs were checked against the prose); tables and animations are machine output and were read, not re-run; No other defect found. Status raised to self-checked; verified needs a textbook or human check.

last verified: never