Parameter Passing

Call by value

Call-by-value is perhaps the simplest of all parameter passing semantics. When a function or procedure is called, its parameters are assigned the values of the arguments given. Upon return from the function or procedure, no modification of the function arguments is performed: the arguments retain their original values.

Since the value of the caller's variable will not be changed when the function or procedure returns, some languages (e.g., Ada and Algol-68) explicitly disallow assignment to a function or procedure parameter. Other languages (e.g., C) allow assignment to the parameter but simply discard any changes made to the parameters; still others permit the use of either behavior (e.g., C++ using the const keyword).

Call by reference

In a call by reference, the argument to the function or procedure must be a variable; then, instead of passing the variable's value to the function or procedure, its address is passed instead. Consequently, any update to the parameter directly affects the caller's variable.

Call by value-result

A call by value-result, like a call by reference, requires that the argument be a variable. However, the parameters of the function or procedure are not directly bound to the variable's address. Rather, they get their own space within their own scope, and when the function or procedure is terminated, the new values of the parameters are copied back into the caller's variables. Hence, value-result differs from reference only in that the values of the caller's variables are not modified while the function or procedure is still in effect--only when it has finished.

One advantage of calling by value-result is that the called procedure or function can safely assign to its parameters without fear of confusing side effects. (This, for example, is not true with a call by reference or by name.) On the other hand, making life easier for the callee makes life more confusing for the caller. Consider the following code:

proc a (int x, int y) {
    x = 1;
    y = 2;
}
int t;
a (t,t);
print t;

The value of t after calling a then depends on the order of parameter copies when the procedure call is finished.

Call by name

A call by name is functionally much like a call by reference, but is slightly different in spirit. Instead of passing the address of a variable as an argument, a language that implements call-by-name (i.e., Algol-60) passes a thunk--a pair of functions that provide read and write access to the variable. In the case of simple variables (i.e., not arrays), call-by-name yields the same results as call-by-reference. When an argument to a function or procedure is an array element, however, its index expression is re-evaluated in the caller's scope every time the parameter is used within the function or procedure. Indeed, the swap example below is perhaps the canonical example of the perils of call-by-name. In fact, with call-by-name it is impossible to implement a "foolproof" version of such a swap function.

The basic difficulty with call-by-name results from the side effects that assignment via thunk can cause. For example, if an array element is passed as an argument, the precise element of the array being referred to can depend on the state; hence, if the state changes, so does the element being referred to. In the example, both i and a[i] are passed as arguments to the same function; then, when i changes, a[i] refers to a different element of the array from that which was (probably) intended.

Call by textual substitution

In a call by textual substitution (or macro substitution), the parameters of the function or procedure are bound to the exact text of the arguments. This differs from call-by-name only in that it does not perform lambda capture--that is, if a variable local to the function or procedure has the same name as an argument, the two will indeed interfere (which will not happen in a call-by-name).

Parameter passing example

Here is some example code to illustrate the various forms of parameter passing. (Assume the array indices start from 0, so a[1] = 2.) What is the output of the program under each of the five different forms of parameter passing?

int i = 1;
int a[] = {4, 2, 3, 0, 1};

void print(...);

void swap(int x, int y) {
    int t;
    int i = 0;
    t = x;
    x = y;
    y = t;
    print(x, y, i, a);
}

swap(i, a[i]);
print(i, a);