The central notion underlying the concept of a procedure is abstraction. The programmer abstracts common operations relative to a small set of names, or parameters. To use the operation, the programmer invokes the procedure with an appropriate binding of values to those parameters. The called procedure also needs a mechanism to return its result. If it produces a result suitable for assignment, we say that itreturns a value and we call the procedure afunction rather than a plain procedure.
7.4.1 Passing Parameters
Parameter naming lets the programmer write the procedure in terms of its local name space, and then invoke it in many different contexts to operate on many different arguments. This notion of mapping arguments from the call into the procedure’s name space is critical to our ability to write abstract, modular codes. Most modern programming languages use one of two rules for mapping ac- tual parameters at a call site into the formal parameters declared inside the called procedure, call-by-value binding and call-by-reference binding. While these techniques differ in their behavior, the distinction between them is best explained by understanding their implementation. Consider the following pro- cedure, written inc, and several call sites that invoke it.
int fee(x,y) int x,y; { x = 2 * x; y = x + y; return y; } c = fee(2,3); a = 2; b = 3; c = fee(a,b); a = 2; b = 3; c = fee(a,a);
With call-by-value, as in c, the calling procedure creates a location for the formal parameter in the called procedure’sarand copies the value of the actual parameter into that location. The formal parameter has its own storage and its own value. The only way to modify its value is by referring directly to its name. The sole constraint placed on it is its initial value, which is determined by evaluating the actual parameter at the time of the call.
The three different invocations produce the same results under call-by-value.
Call by a b Return
Value in out in out Value
fee(2,3) – – – – 7
fee(a,b) 2 2 3 3 7
fee(a,a) 2 2 3 3 6
Because the actual parameters and formal parameters have the same value, rather than the same address, the behavior is both intuitive and consistent. None of the calls changes the value of eitheraorb.
7.4. COMMUNICATING VALUES BETWEEN PROCEDURES 179
Digression: Call-by-Name Parameter Binding
Algol introduced the notion of call-by-name parameter binding. Call-by- name has a simple meaning. A reference to the formal parameter behaves exactly as if the actual parameter had been textually substituted in place of the formal. This can lead to some complex and interesting behavior. Consider the following short example in Algol-60:
begin comment Call-by-Name example; procedure zero(Arr,i,j,u1,u2);
integer Arr; integer i,j,u1,u2; begin;
for i := 1 step 1 until u1 do for j := 1 step 1 until u2 do
Arr := 0; end;
integer array Work[1:100,1:200]; integer p, q, x, y, z;
x := 100; y := 200;
zero(Work[p,q],p,q,x,y); end
The procedure zeroassigns the value 0 to every element of the arrayWork. To see this, rewrite zerowith the text of the actual parameters.
This elegant idea fell from use because it was difficult to implement. In general, each parameter must be compiled into a small function of the for- mal parameters that returns a pointer. These functions are called thunks. Generating competent thunks was complex; evaluating thunks for each ac- cess to a parameter raised the cost of parameter access. In the end, these disadvantages overcame any extra generality or transparency that the simple rewriting semantics offered.
In a language that uses call-by-reference parameter passing, the calling pro- cedure creates a location for a pointer to the formal parameter and fills that location with a pointer to the result of evaluating the expression. Inside the called procedure, references to the formal parameter involve an extra level of indirection to reach the value. This has two critical consequences. First, any redefinition of the call-by-reference formal parameter is reflected in the actual parameter, unless the actual parameter is an expression rather than a variable reference. Second, any call-by-reference formal parameter might be bound to a variable that is accessible by another name inside the called procedure.
the call-by-reference parameter binding.
procedure fee(x,y) returns fixed binary; declare x,y fixed binary; begin; x = 2 * x; y = x + y; return y; end; c = fee(2,3); a = 2; b = 3; c = fee(a,b); a = 2; b = 3; c = fee(a,a);
Pl/i’s call-by-reference parameter passing produces different results than thec code did.
Call by a b Return
Reference in out in out Value
fee(2,3) – – – – 7
fee(a,b) 2 4 3 7 7
fee(a,a) 2 8 3 3 8
Notice that the second call redefines both a and b; the behavior of call-by- reference is intended to communicate changes made in the called procedure back to the calling environment. The third call creates an alias betweenxandy
infee. The first statement redefinesato have the value 4. The next statement references the value of atwice, and adds the value of ato itself. This causes
feeto return the value 8, rather than 6.
In both call-by-value and call-by-reference, the space requirements for repre- senting parameters are small. Since the representation of each parameter must be copied into thearof the called procedure on each call, this has an impact on the cost of the call. To pass a large object, most languages use call-by-reference for efficiency. For example, inc, the string representation passes a fixed-size pointer rather than the text. C programmers typically pass a pointer to the array rather than copying each element’s value on each call.
Some Fortran compilers have used an alternative binding mechanism known as call-by-value/result. The call operates as in call-by-value binding; on exit, values from the formal parameters are copied back to the actual parameters— except when the actual is an expression.
Call-by-value/result produces the same results as call-by-reference, unless the call site introduces an alias between two or more formal parameters. (See Chapter 13 for a deeper discussion of parameter-based aliasing.) The call- by-value/result binding scheme makes each reference in the called procedure cheaper, since it avoids the extra indirection. It adds the cost of copying values in and out of the called procedure on each call.
7.4. COMMUNICATING VALUES BETWEEN PROCEDURES 181
integer function fee(x,y) integer x, y x = 2 * x y = x + y return y end c = fee(2,3) a = 2 b = 3 c = fee(a,b) a = 2 b = 3 c = fee(a,a)
Since the Fortran standard forbids aliasing that involves formal parameters, this program is not a legal Fortran program. The standard allows the compiler to interpret a non-standard conforming program in any convenient way. How- ever, most Fortran compilers will neither detect this particular problem, nor implement it incorrectly.
A Fortran version of our program, implemented with value/result, would produce the following behavior.
Call by a b Return
Value/Result in out in out Value
fee(2,3) – – – – 7
fee(a,b) 2 4 3 7 7
fee(a,a) 2 * 3 3 6
Note that, for the third call site, the value foraafter the call is dependent on the call-by-value/result implementation. Depending on the order of assignment to a,acould have the value 6 or 4.
7.4.2 Returning Values
To return a value for the function, as opposed to changing the value of one of its actual parameters, the compiler must set aside space for the returned value. Because the return value, by definition, is used after the called procedure terminates, it needs storage outside the called procedure’sar. If the compiler- writer can ensure that the return value is of small, fixed size, then it can store the value in either the ar of the calling procedure (at a fixed offset) or in a register.
To achieve this goal, the compiler can allocate a fixed slot in the ar of the calling procedure or a designated hardware register, and use that slot to hold a pointer to the actual value. This allows the called routine to return an arbitrarily-sized value to the caller; space for the return value is allocated in the caller’s arprior to the call and the appropriate pointer is stored in the return value slot of the caller’sar. To return the value, the called procedure loads the pointer fromarp+offset(return value), and uses it to store the return value.
This scenario can be improved. If the return value is a small, simple value such as a simple integer or a floating-point number, it can be returned in the slot allocated for the pointer. As long as both the caller and the callee know the type of the returned value, the compiler can safely and correctly eliminate the indirection.