In a Java program, all parameters are passed by value.
However, there are three other parameter-passing modes that have been
used in programming languages:
First, here's some useful terminology:
Parameters can only be passed by value in Java and in C.
In Pascal, a parameter is passed by value unless the corresponding
formal has the keyword var;
similarly, in C++, a parameter is passed by value unless the corresponding
formal has the symbol & in front of its name.
For example, in the Pascal and C++ code below,
parameter x is passed by value, but not parameter y:
What happens when the following Java program executes?
When a parameter is passed by reference, the calling method copies the
l-value of the argument into the called method's AR (i.e., it copies
a pointer to the argument instead of copying the
argument's value).
If the argument has no l-value (e.g., it is an expression like x+y),
the compiler may consider this an error (that is what happens in Pascal,
and is also done by some C++ compilers),
or it may give a warning, then generate code to evaluate the expression,
to store the result in some temporary location, and to copy the address
of that location into the called method's AR (this is is done by some C++
compilers).
In terms of language design, it seems like a good idea to consider this
kind of situation an error.
Here's an example of code in which an expression with no l-value is
used as an argument that is passed by reference (the example was actually
a Fortran program, but Java-like syntax is used here):
To understand why reference parameters are useful, remember that, although
in Java, all non-primitive types are really pointers,
that is not true in other languages.
For example, consider the following C++ code:
This motivates some reasons for using reference parameters:
Consider writing a method to sort the values in an array of integers.
An operation that is used by many sorting algorithms is to swap the
values in two array elements.
This might be accomplished using a swap method:
It is important to realize that the code generator will generate
different code for a use of a parameter in a method, depending on
whether it is passed by value or by reference.
If it is passed by value, then it is in the
called method's AR (accessed using an offset from the FP).
However, if it is passed by reference, then it is in some other storage
(another method's AR, or in the static data area). The value in the called
method's AR is the address of that other location.
To make this more concrete, assume the following code:
Overview
We will consider each of those modes, both from the point of view of the
programmer and from the point of view of the compiler writer.
L-values and r-values get their names from the Left and Right sides of an
assignment statement.
For example, the code generated for the statement x = y uses the
l-value of x (the left-hand side of the assignment) and the r-value of y
(the right-hand side of the assignment).
Every expression has an r-value.
An expression has an l-value iff it can be used on the left-hand side
of an assignment.
void f(int a, boolean b, int c)
we will use the terms parameters, formal parameters,
or just formals to refer to a, b, and c.
f(x, x==y, 6);
we will use the terms arguments, actual parameters,
or just actuals to refer to x, x==y, and 6.
expression
r-value
x 2
y 3
x+y 6
x==y false
Value Parameters
// Pascal procedure header
Procedure f(x: integer; var y: integer);
// C++ function header
void f(int x; int & y);
When a parameter is passed by value, the calling method copies the r-value
of the argument into the called method's AR.
Since the called method only has access to
the copy, changing a formal parameter (in the called method) has
no effect on the corresponding argument.
Of course, if the argument is a pointer, then changing the
"thing pointed to" does have an effect that can be
"seen" in the calling procedure.
For example, in Java, arrays are really pointers, so if an array is
passed as an argument to a method, the called method
can change the contents of the array,
but not the array variable itself, as illustrated below:
void f( int[] A ) {
A[0] = 10; // change an element of parameter A
A = null; // change A itself (but not the corresponding actual)
}
void g() {
int[] B = new int [3];
B[0] = 5;
f(B);
//*** B is not null here, because B was passed by value
//*** however, B[0] is now 10, because method f changed the first element
//*** of the array pointed to by B
}
class Person {
int age;
String name;
}
class Test {
static void changePerson(Person P) {
P.age = 10;
P = new Person();
P.name = "Joe";
}
public static void main(String[] args) {
Person P = new Person();
P.age = 2;
P.name = "Ann";
changePerson(P);
System.out.println(P.age);
System.out.println(P.name);
}
}
Reference Parameters
void mistake(int x) { // x is a reference parameter
x = x+1;
}
void main() {
int a;
mistake(1);
a = 1;
print(a);
}
When this program was compiled and executed, the output was 2! That
was because the Fortran compiler stored 1 as a literal at some address
and used that address for all the literal "1"s in the program. In
particular, that address was passed when "mistake" was called, and was
also used to fetch the value to be assigned into variable a. When
"mistake" incremented its parameter, the location that held the value
1 was incremented; therefore, when the assignment to a was executed,
the location no longer held a 1, and so a was initialized to 2!
class Person {
public:
String name;
int age;
};
void birthday(Person per) {
per.age++;
}
void main() {
Person P;
P.age = 0;
birthday(P);
print(P.age);
}
Note that in main, variable P is a Person, not
a pointer to a Person;
i.e., main's activation record has space for P.name and
P.age.
Parameter per is passed by value (there is no ampersand), so
when birthday is called from main, a copy of variable
P is made (i.e., the values of its name and age
fields are copied into birthday's AR).
It is only the copy of the age field that is updated by
birthday, so when the print statement in main is
executed, the value that is output is 0.
static void swap(int x, int y) {
int tmp = x;
x = y;
y = tmp;
}
Assume that A is an array of integers, and that j and
k are (different) array indexes.
Draw a picture to illustrate what happens when the call:
swap(A[j], A[k]);
is executed, assuming
void f(int a) {
a = a - 5;
}
void main() {
int x = 10;
f(a);
}
Below is the code that would be generated for the statement
a = a - 5,
assuming (1) that a is passed by value and (2) assuming that
a is passed by reference:
(1) passed by value | (2) passed by reference
------------------- | -----------------------
|
lw t0,(FP) #load a's r-value into t0 | lw t0,(FP) #load a's l-value into t0
sub t0,t0,5 #t0 = t0 - 5 | lw t1,(t0) #load a's r-value into t1
sw t0,(FP) #store result into f's AR | sub t1,t1,5 #t1 = t1 - 5
| sw t1,(t0) #store result into main's AR
The code generator will also generate different code for a method call depending on whether the arguments are to be passed by value or by reference. Consider the following code:
int x, y; x = y = 3; f(x, y);Assume that f's first parameter is passed by reference, and that its second parameter is passed by value. What code would be generated to fill in the parameter fields of f's AR?
Value-result parameter passing was used in Fortran IV and in Ada.
The idea is that, as for pass-by-value, the value (not the address) of
the actual parameters are copied into the called method's AR.
However, when the called method ends, the final values of the parameters
are copied back into the arguments.
Value-result is equivalent to call-by-reference except
when there is aliasing (note: "equivalent" means the program will produce
the same results, not that the same code will be generated).
Two expressions that have the same l-value are called aliases.
Aliasing can happen:
Pointer manipulation can create aliases,
as illustrated by the following Java code.
(Note: this kind of aliasing does not make pass-by-reference different
from pass-by-value-result; it is included here only for completeness of
the discussion of aliasing.)
This way of creating aliases (and the difference between reference parameters
and value-result parameters in the presence of this kind of aliasing)
are illustrated by the following C++ code:
Consider the following C++ code:
Value-Result Parameters
Will will look at examples of each of these below.
Creating Aliases via Pointers
Person p, q;
p = new Person();
q = p;
// now p.name and q.name are aliases (they both refer to the same location)
// however, p and q are not aliases (they refer to different locations)
pictorially:
+--------------------------------+
| |
| +---------------------+ |
| | | |
| | v v
--- | | +---+
p: |-|-----+ | name: | | a Person object
--- | |---|
| age: | |
--- | +---+
q: |-|-------------+
---
Creating Aliases by Passing Globals as Arguments
int x = 1; // a global variable
void f(int & a)
{ a = 2; // when f is called from main, a and x are aliases
x = 0;
}
main()
{ f(x);
cout << x;
}
As stated above, passing parameters by value-result yields the same results
as passing parameters by reference except when there is
aliasing.
The above code will print different values when f's parameter is
passed by reference than when it is passed by value-result.
To understand why, look at the following pictures, which show the effect
of the code on the activation records (only variables and parameters are
shown in the ARs, and we assume that variable x is in main's
AR):
Call-by-reference Call-by-value-result
at time of call
_______ _______
f's AR a: | --------+ f's AR a: | 1 |
|_______| | |_______|
main's AR x: | 1 <-----+ main's AR x: | 1 |
|_______| |_______|
after a = 2
_______ _______
a: | --------+ a: | 2 |
|_______| | |_______|
x: | 2 <-----+ x: | 1 |
|_______| |_______|
after x = 0
_______ _______
a: | --------+ a: | 2 |
|_______| | |_______|
x: | 0 <-----+ x: | 0 |
|_______| |_______|
When f returns, the final value of value-result
parameter "a" is copied back into the space for "x", so:
after call
_______ _______
x: | 0 | x: | 2 |
|_______| |_______|
output
0 2
Creating Aliases by Passing Same Argument Twice
void f(int &a, &b)
{ a = 2;
b = 4;
}
main()
{ int x;
f(x, x);
cout << x;
}
Assume that f's parameters are passed by reference.
In this case,
when main calls f, a and b are aliases.
As in the previous example, different output may be produced in this
case than would be produced if f's parameters were passed by
value-result (in which case, no aliases would be created by the
call to f, but there would be a question as to the order in
which values were copied back after the call).
Here are pictures illustrating the difference:
Call-by-reference Call-by-value-result
at time of call
_______ _______
/ b: | --------+ b: | ? |
f's AR | |_______| | |_______|
\ a: | ------+ | a: | ? |
|_______| | | |_______|
main's AR x: | ? <---+-+ x: | ? |
|_______| |_______|
after a = 2
_______ _______
b: | --------+ b: | ? |
|_______| | |_______|
a: | ------+ | a: | 2 |
|_______| | | |_______|
x: | 2 <---+-+ x: | ? |
|_______| |_______|
after b = 4
_______ _______
b: | --------+ b: | 4 |
|_______| | |_______|
a: | ------+ | a: | 2 |
|_______| | | |_______|
x: | 4 <---+-+ x: | ? |
|_______| |_______|
after call
_______ _______
x: | 4 | x: | ??? |
|_______| |_______|
output
4 ???
With value-result parameter passing, the value of x after the call is
undefined, since it is unknown whether a or b gets copied
back into x first. This may be handled in several ways:
Assume that all parameters are passed by value-result.
Question 1: Give a high-level description of what the code generator must do for a method call.
Question 2: Give the specific code that would be generated for the call shown below, assuming that variables x and y are stored at offsets -8 and -12 in the calling method's AR.
int x, y; f(x, y);