CSE 341 -- Parameter Passing

The following techniques are used to pass arguments in traditional imperative languages: Other techniques:

call by value: copy going into the procedure

call by result: copy going out of the procedure

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

call by reference: pass a pointer to the actual parameter, and indirect through the pointer. If the actual parameter is a variable or an array element (not an expression), then the procedure can assign to the formal parameter and as a result assign into the actual parameter as well. (The more general programming language term for "variable or array element" or the like is l-value, in other words, something that can be on the left hand side of an assignment statement.)

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. One possible implementation: use anonymous function ("thunk") for call by name expressions. (This is traditionally used to implement call by name in languages such as Algol and Simula.)

Call by name is not supported in recent languages, but is still an important concept in programming language theory. (In newer languages, if you want the effect of call by name, pass a procedure as a parameter.)

For a pure functional language, lazy evaluation has exactly the same semantics as call by name, but will generally be more efficient. With lazy evaluation, the actual parameter is not evaluated until the formal parameter's value is needed. At that time, the actual parameter is evaluated, and the value is cached. Then, if the value of the formal parameter is needed again, the cached value will be used. (This is safe in a pure functional language, since the value of an expression will always be the same each time it is evaluated.) Thus, with lazy evaluation, an actual parameter is evaluated either 0 or 1 times.

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. Java uses call by value for primitive data types.

Call by reference is particularly efficient for large pieces of data (such as large arrays), since they don't need to be copied.

Fortran uses call by reference
Insecurity in early FORTRANs -- passing a constant allowed the procedure to change the constant!

Algol 60 has call by name, call by value

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

For objects (not primitive types), Java uses call-by-value with pointer semantics. In other words, Java passes a copy of the reference to the object to the called method (but doesn't copy the object itself). Smalltalk and Scheme work in exactly the same way. Unless you assign into the formal parameter, this gives the same answers as call-by-reference. However, if you assign into the formal parameter, the link with the actual parameter is broken. (Actually, Smalltalk won't let you assign into the formal parameter, but Java and Scheme will.)

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

The following examples are in an Algol-like language.
    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

Examples of Parameter Passing in Java

As noted above, Java uses call by value. For primitive types, this works exactly as described above. For objects, Java uses call-by-value with pointer semantics. Here are two examples, adapted from The Java Programming Language by Ken Arnold and James Gosling.

Passing a primitive type as a parameter:

class PassByValue {
  public static void main(String[] args) {
    double x = 1.0;
    System.out.println("before: x = " + x);
    halveIt(x);
    System.out.println("after: x = " + x);
  }

  public static void halveIt(double arg) {
    arg = arg/2.0;
    System.out.println("halved: arg = " + arg);
  }
}
Sample output:
before: x = 1.0
halved: arg = 0.5
after: x = 1.0
Here is another example, in which we pass an object as a parameter.
import java.awt.Point;

class PassObject {
  public static void main(String[] args) {
    Point p = new Point(10,20);
    System.out.println("before halveX: p.x = " + p.x);
    halveX(p);
    System.out.println("after halveX: p.x = " + p.x);

    setIt(p);
    System.out.println("after setIt: p.x = " + p.x);
  }

  public static void halveX(Point q) {
    q.x = q.x/2;
    System.out.println("halved: q.x = " + q.x);
  }

  public static void setIt(Point q) {
    q = new Point(0,0);
    System.out.println("setIt: q.x = " + q.x);
  }
}
Sample output:
before halveX: p.x = 10
halved: q.x = 5
after halveX: p.x = 5
setIt: q.x = 0
after setIt: p.x = 5