CSE 341 -- Parameter Passing

Almost every language has some method for passing parameters to functions and procedures. These mechanisms have evolved over times, and there are a number of important differences.

What you should know: The two most common methods for parameter passing in modern imperative languages are call by value and call by reference. Call by name is primarily of historical interest, but is closely related to the way that parameters are handled when macros are expanded, and has the same semantics as lazy evaluation in functional languages. Languages with input/output parameters support call by value-result which differs subtly from call by reference

Techniques used for argument passing in traditional imperative languages:

Other techniques:

When talking about parameters, the formal parameters are the parameters associated with the function, and the actual parameters are the parameters that are used to call the function.

Call by value, by result, by value-result, are special cases of call by copy, where values are copied into and out of the procedure parameters.

call by value: copy going into the procedure. This is the mechanism used by both C and Java. Note that this mechanism is used for passing objects, where a reference to the objected is passed by value.

call by result: copy going out of the procedure, the formal parameters are copied back into the actual parameters. (Note that this only makes sense if the actual parameter is a variable, or has a l-value, such as an array element.)

call by value result: copy going in, and again going out

call by reference: The actual parameters and formal parameters are identified. The natural mechanism for this is to pass a pointer to the actual parameter, and indirect through the pointer

call by name: re-evaluate the actual parameter on every use. For actual parameters that are simple variables, this is the same as call by reference. For actual parameters that are expressions, the expression is re-evaluated on each access. It should be a runtime error to assign into a formal parameter passed by name, if the actual parameter is an expression. Implementation: use anonymous function ("thunk") for call by name expressions

Call by value is particularly efficient for small pieces of data (such integers), since they are trivial to copy, and since access to the formal parameter can be done efficiently.

Call by reference is particularly efficient for large pieces of data (such as large arrays), since they don't need to be copied. Call by reference also allows a procedure to manipulate the values of variables of the caller, such as in a swap routine.

Fortran uses call by reference, early versions of FORTRAN allowed constants to change their values, since they were also passed by reference!

Algol 60 has call by name, call by value

Ada uses different designations: IN, OUT, IN OUT:

Scheme, Lisp, and Smalltalk use call-by-value with pointer semantics

C generally uses call-by-value, although there are inconsistencies in the language. In particular, compare how structures are passed, and how arrays are passed. (It can be argued that arrays are passed by reference.)

An important related concept: aliasing. Two variables are aliased if they refer to the same storage location. A common way that aliasing can arise is using call by reference. A formal parameter can be aliased to a nonlocal variable, or two formal parameters can be aliased.


Example of call by value versus value-result versus reference

    begin
    integer n;
    procedure p(k: integer);
        begin
        n := n+1;
        k := k+4;
        print(n);
        end;
    n := 0;
    p(n);
    print(n);
    end;
Note that when using call by reference, n and k are aliased.

Output:

call by value:        1 1
call by value-result: 1 4
call by reference:    5 5


Example of call by reference versus call by name

    begin
    array a[1..10] of integer;
    integer n;
    procedure p(b: integer);
        begin
        print(b);
        n := n+1;
        print(b);
        b := b+5;
        end;
    a[1] := 10;
    a[2] := 20;
    a[3] := 30;
    a[4] := 40;
    n := 1;
    p(a[n+2]);
    new_line;
    print(a);
    end;

Output:

call by reference:  30 30 
                    10 20 35 40                    

call by name:       30 40
                    10 20 30 45

Call by name and macro substitution

Call by name is closely related to macro substitution - and macro substitution shares some of the problems of call by name (and has additional problems). The swap function can be written with the following C macro:

#define swap(A, B) {int xyztmp; xyztmp = A; A = B; B = xyztmp;}

The preprocessor expands
swap(x, y);
to the code
{ int xyztmp; xyztmp = x; x = y; y = xyztmp; }
which is then compiled, saving precious micro seconds of execution time by avoiding the function call. However, the call
swap(i, z[i]);
expands to
{ int xyztmp; xyztmp = i; i = z[i]; z[i] = xyztmp; }
which almost certainly is wrong.

Call by name is more sophisticated than macro substitution, in that variable name conflicts are taken care of between actual parameters, and the local variables of the function. The swap problem is the classic case where call-by-name does the wrong thing. Call by name is also expensive to implement.