SEN2001 · Week 8 · Subprograms I

Subprograms I: parameter passing methods

Formal/actual parameters, the in/out/inout models and five implementations: pass-by-value, result, value-result, reference, name. Tracing what the same program prints under each method; also Java's rule, closures and default parameters.

subprogramformal parameteractual parameterpositional parameterkeyword parameterdefault parameterin modeout modeinout modepass-by-valuepass-by-resultpass-by-value-resultpass-by-referencepass-by-namealiasingthunkclosureoverloaded subprogramgeneric subprogram

This week’s topic, parameter passing methods, is one of the most “mechanical” topics on the exam and one of the biggest point winners. The question template is always the same: a small procedure, a call, “what happens to the variables under each method?” Recipe: for each method write what the formal parameter receives (a value, an address, an expression) and when it writes back, then trace line by line.

Golden rules:

  1. Value: a copy. Reference: an address. One does not change the outside, the other changes it immediately.
  2. Value-result changes the outside only at return, and the result may depend on the copy order.
  3. Name: re-evaluate the expression on every use. If the index variable changes, you go to a different element.
  4. If the same variable is passed to two parameters, or as parameter + global, there is an alias. Most questions come from here.
  5. In Java everything is pass-by-value, including references.

Basic concepts

A subprogram definition consists of a header (name, parameters) and a body. The parameters in the definition are formal parameters, the expressions in the call are actual parameters.

  • Positional: actuals are matched in order. Short, but causes mistakes in long lists.
  • Keyword: area(width = 3, height = 5) (Python, Ada). The order is free, but the formal names must be known.
  • Default: the value used if no actual is given.
function f(a, b = a * 2) { return a + b; }
console.log(f(3), f(3, 1));
9 4

Subprograms are divided into functions, which return a value, and procedures, which do not. Local variables are usually stack-dynamic (recursion is possible); in C they can be made static with static (they remember history).

Semantic models: in, out, inout

ModelData flowImplementation methods
In modeFrom caller to calleePass-by-value
Out modeFrom callee to callerPass-by-result
Inout modeTwo-wayPass-by-value-result, pass-by-reference, pass-by-name

Implementation is done in two ways: physically copying the value (value, result, value-result) or transferring an access path (address) (reference).

Five methods

Pass-by-value

The actual’s value initializes the formal, and the formal behaves like a local variable. No effect on the outside. Cost: large objects are copied.

Pass-by-result

The input value is not transferred. The formal is initialized in the callee, and its value is copied to the actual on return. Problems: in calls like g(a, a), which copy will win (actual parameter collision), and whether the address of the actual is computed at the call or at the return.

void g(out int x, out int y) { x = 5; y = 7; }
g(a, a);          copy left to right: a = 5, then a = 7  → 7

Pass-by-value-result

Value + result: copy in at entry, copy out at return. Similar to reference but alias effects are not visible until the return.

Pass-by-reference

The address of the actual is transferred, and the formal becomes its alias. No copy, fast. But aliases lower readability and reliability; every access needs an indirection.

Pass-by-name

Every use of the formal behaves as if it were replaced by the text of the actual, and is evaluated at that moment in the caller’s environment. It exists in ALGOL 60 and is rarely used today. Its implementation: a thunk for each parameter (a small function that computes the address or value when called). When a simple variable is passed it behaves like reference; the difference appears when an array element or an expression is passed.

Worked example 1: the same variable twice

void f(int x, int y) { x = x + 1; y = y * x; }
a = 2;  f(a, a);
MethodTraceFinal a
Valuex = 2, y = 2 copies; a is untouched2
Referencex and y = the cell of a. a = 3, a = 3 · 39
Value-result (left to right)x = 3, y = 2 · 3 = 6; return: a = 3, then a = 66
Value-result (right to left)the same, return: a = 6, then a = 33
Namex and y are a on every use: same as reference9

Worked example 2: alias with a global

int g = 3;
void fun(int x) { x = x * 2; g = g + 1; }
fun(g);
value:         x = 3 → 6 (local)       g = 3 + 1          → 4
reference:     x ≡ g: g = 6            g = 6 + 1          → 7
value-result:  x = 3 → 6 (local)       g = 4; on return g = x → 6
name:          x ≡ g (simple variable)                     → 7

Worked example 3: array element and a changing index

int i = 0;  int A[3] = {1, 2, 3};
void h(int x) { i = i + 1; x = x + 10; }
h(A[i]);
value:      the array does not change                 {1, 2, 3}
reference:  address at the call: x ≡ A[0]             {11, 2, 3}
            the change of i is irrelevant
name:       x is A[i] on every use; i is now 1        {1, 12, 3}
            x = x + 10  →  A[1] = A[1] + 10

With value-result you also get {11, 2, 3}, because the return address is computed at call time. In some definitions the address is computed at return; then it would be {1, 11, 3}. If the question does not specify it on the exam, assume call time and write that down.

Worked example 4: the classic swap(i, A[i])

void swap(int x, int y) { int t = x; x = y; y = t; }
i = 1;  A = {5, 0, 7};
swap(i, A[i]);
reference (addresses at the call: x ≡ i, y ≡ A[1]):
  t = i = 1;  i = A[1] = 0;  A[1] = t = 1      → i = 0, A = {5, 1, 7}   correct swap

name (re-evaluate on every use):
  t = i            → 1
  i = A[i]         → i = A[1] = 0
  A[i] = t         → A[0] = 1      ← i is now 0
                                    → i = 0, A = {1, 0, 7}   swap BROKEN

A general swap that works correctly cannot be written with pass-by-name. One of the reasons this method was abandoned.

PITFALL · thinking name and reference are the same. When a simple variable (a) is passed, the two give the same result. When an array element is passed and the index changes in the body, they differ: reference writes to the old element, name writes to the new element.

The situation in languages

LanguageRule
CPass-by-value. For a reference effect a pointer is passed (swap(&x, &y))
C++Value + reference types (int&); const int& passes large objects without copying but without modification
JavaEverything is pass-by-value. Because object variables hold references, a copy of the reference is passed
C#Default value; ref for reference, out similar to result
Adain, out, in out modes are written explicitly
Python, RubyPass-by-assignment: the reference to the object is copied (similar to Java)
static void swap(int a, int b) { int t = a; a = b; b = t; }
static void change(int[] arr) { arr[0] = 99; arr = new int[]{7, 7}; arr[1] = 55; }
static void grow(StringBuilder sb) { sb.append("!"); sb = new StringBuilder("new"); sb.append("?"); }

int x = 1, y = 2; swap(x, y);
System.out.println(x + " " + y);
int[] a = {1, 2}; change(a);
System.out.println(a[0] + " " + a[1]);
StringBuilder s = new StringBuilder("hi"); grow(s);
System.out.println(s);
1 2
99 2
hi!

Rule: a method can change the inside of an object (because it points to the same object), but cannot point the caller’s variable at a different object.

Closures and other topics

A closure is the combination of a subprogram and the referencing environment where it was defined. If a function can be returned as a value and called elsewhere, the lifetime of the variables it captured must be extended (unlimited extent).

function makeCounter() { let c = 0; return () => ++c; }
const c1 = makeCounter(), c2 = makeCounter();
console.log(c1(), c1(), c2(), c1());
const fs = []; for (var i = 0; i < 3; i++) fs.push(() => i);
console.log(fs.map((g) => g()).join(","));
const gs = []; for (let j = 0; j < 3; j++) gs.push(() => j);
console.log(gs.map((g) => g()).join(","));
1 2 1 3
3,3,3
0,1,2
  • Overloaded subprogram: the same name, different parameter profiles (Java, C++). Which one is called is determined at compile time.
  • Generic subprogram: a single definition that works with different types (Java generics, C++ templates). Parametric polymorphism.
  • Variable number of parameters: Java int... xs, C printf.

Common mistakes

WrongCorrect
Value-result = referenceThe alias effect is not visible until return; f(a, a) gives a different result
Copy order is irrelevant in value-resultIt matters in f(a, a): left to right 6, right to left 3
Name = referenceThey differ with a changing index
In reference the address is computed on every accessOnce, at call time
Java objects are pass-by-referenceA copy of the reference; arr = new does not change the outside
In result mode the input value is usedThe formal is not initialized
A line that changes a global is ineffective under valueThe global changes directly; only the formal is a copy

Test yourself

Exam-style questions. Solve them on paper first, then reveal the answer.

Question 1medium

Given the procedure below and a global a = 2, what is the value of a after the call f(a, a) under (a) pass-by-value, (b) pass-by-reference?

void f(int x, int y) {
    x = x + 1;
    y = y * x;
}
Show answer
(a) 2 (b) 9

Value: x and y are copies of a, a is untouched: 2. Reference: x and y are both bound to the address of a (the same cell, aliases). x = x + 1 → a = 3. y = y * x → a = 3 * 3 = 9.

Question 2hard

Same procedure and call f(a, a) with a = 2, but pass-by-value-result, copying back from left to right. What is a? What if copying is right to left?

Show answer
6; right to left gives 3

At entry x = 2, y = 2 (copies). x = 3. y = 2 * 3 = 6. On return copy back: first x → a (a = 3), then y → a (a = 6). If you copy from right to left, first y (6), then x (3): a = 3. When the same variable is passed twice, the result depends on the copy order; the language must define this.

Question 3medium

Using pass-by-result with g(a, a), where void g(out int x, out int y) { x = 5; y = 7; }, and copying back left to right, what is a?

Show answer
7

In pass-by-result the input value is not transferred, x and y are initialized in the callee. On return x → a (5), then y → a (7). The last copy wins. This situation is called an actual parameter collision: the result depends on the copy order, and if the language does not define this order, the program is not portable.

Question 4hard

With i = 1 and A = {5, 0, 7} (0-based), what are i and A after swap(i, A[i]) using pass-by-name?

void swap(int x, int y) {
    int t = x;
    x = y;
    y = t;
}
Show answer
i = 0, A = {1, 0, 7}

In pass-by-name every use of x and y is textually replaced with i and A[i] and evaluated at that moment.

t = i          t = 1
i = A[i]       i = A[1] = 0
A[i] = t       A[0] = 1     (i is now 0!)
The swap fails: the desired result was i = 0, A[1] = 1. The same call with reference does the swap correctly: i = 0, A = {5, 1, 7}.

Question 5hard

Global i = 0, A = {1, 2, 3}. What is A after h(A[i]) under (a) reference, (b) name?

void h(int x) {
    i = i + 1;
    x = x + 10;
}
Show answer
(a) {11, 2, 3} (b) {1, 12, 3}

Reference: the address is computed at call time, x = the address of A[0]. The change of i does not affect x: A[0] = 1 + 10 = 11. Name: x is re-evaluated as A[i] on every use; i is now 1: A[1] = A[1] + 10 = 12. With value the array does not change.

Question 6hard

Global g = 3. What is g after fun(g) under value, reference and value-result?

void fun(int x) {
    x = x * 2;
    g = g + 1;
}
Show answer
value: 4, reference: 7, value-result: 6

Value: x is a copy, only g = g + 1 is effective: 4. Reference: x and g are the same cell (alias): x = 6 → g = 6, then g = 7. Value-result: x = 3 copy, x = 6; g = 4; on return x is copied to g and overwrites g = 4: 6.

Question 7easy

What is printed in Java?

static void swap(int a, int b) { int t = a; a = b; b = t; }
// in main:
int x = 1, y = 2;
swap(x, y);
System.out.println(x + " " + y);
Show answer
1 2

In Java all parameters are pass-by-value. a and b are copies of x and y; the swap happens between the copies. In Java you cannot write a method that swaps two ints.

Question 8medium

What is printed in Java?

static void change(int[] arr) {
    arr[0] = 99;
    arr = new int[]{7, 7};
    arr[1] = 55;
}
// in main:
int[] a = {1, 2};
change(a);
System.out.println(a[0] + " " + a[1]);
Show answer
99 2

The reference itself is passed by value. arr[0] = 99 changes the same object, visible from a. arr = new ... only points the local copy at another object; a still points to the old array. arr[1] = 55 is written to the new array. The result is 99 2.

Question 9medium

What does this JavaScript code print?

function makeCounter() { let c = 0; return () => ++c; }
const c1 = makeCounter(), c2 = makeCounter();
console.log(c1(), c1(), c2(), c1());
Show answer
1 2 1 3

Closure: the returned function carries along the variable c of the environment where it was created. The lifetime of c does not end when makeCounter returns (unlimited extent). Each call of makeCounter creates a separate c: c1's counter is 1, 2, 3; c2's is 1.

Question 10easy

What does this JavaScript code print?

function f(a, b = a * 2) { return a + b; }
console.log(f(3), f(3, 1));
Show answer
9 4

Default parameter: if no actual is given, b = a * 2 = 6, 3 + 6 = 9. If given, it is used: 3 + 1 = 4. Default parameters exist in Python, C++, Ada, JavaScript; not in Java (imitated with overloading).

Question 11easy

Which parameter passing method is described: (a) the actual's value initializes the formal, no value is sent back; (b) the formal is an alias of the actual; (c) the actual is textually substituted and re-evaluated at every reference; (d) value copied in at the call and copied out at the return.

Show answer
(a) value (b) reference (c) name (d) value-result

Value = in mode, a physical copy. Reference = inout, an access path (address) is transferred. Name = inout, late binding, implemented with a thunk. Value-result = inout, two-way copy; sometimes called pass-by-copy. Result = out mode.

Question 12hard

What is printed in JavaScript?

const fs = [];
for (var i = 0; i < 3; i++) fs.push(() => i);
console.log(fs.map(g => g()).join(","));
Show answer
3,3,3

All three closures capture the same var i (var has function scope, a single variable). When they are called the loop has ended, i = 3. If it were written with let j, each iteration would be a separate variable and 0,1,2 would be printed.

Verification: The five parameter passing methods were simulated in node with a real memory model (cells, addresses, thunks re-evaluated on every access for by-name) and the program in each question was run in this simulation (verify/SEN2001/08.mjs); the copy-back order and whether the address is computed at the call or at the return were also tested. Java's pass-by-value behavior was run with the JDK (java verify/SEN2001/W08.java), closures and default parameters with node (verify/SEN2001/08b.mjs).

Review card

formal = name in the definition, actual = expression in the call. positional / keyword / default in: value | out: result | inout: value-result, reference, name VALUE: copy in. no effect outside RESULT: no value in, copy out on return. f(a,a) → the last copy wins VALUE-RESULT: copy in + out on return. the result of f(a,a) depends on the COPY ORDER REFERENCE: the address is passed, alias. the address is computed at CALL time NAME: textual substitution, re-evaluate on EVERY USE (thunk) swap(i, A[i]) by name is broken: i changes first, A[i] then refers to another element global + parameter the same variable → alias: value 4, reference 7, value-result 6 type questions Java: EVERYTHING is pass-by-value. the reference is copied: can change the object, a = new ... does not change the outside C: value (reference imitated with a pointer). C++: & reference. C#: ref / out closure: function + the environment it was created in. the lifetime of the captured variable is extended closure with var i → all see the last value; with let each iteration is separate