|
Table of Content | Chapter Eleven (Part 3) |
CHAPTER
ELEVEN: PROCEDURES AND FUNCTIONS (Part 2) |
|
11.5 -
Parameters 11.5.1 - Pass by Value 11.5.2 - Pass by Reference 11.5.3 - Pass by Value-Returned |
11.5.4 -
Pass by Result 11.5.5 - Pass by Name 11.5.6 - Pass by Lazy-Evaluation |
11.5 Parameters | |
Although there is a large class of procedures that are totally self-contained, most procedures require some input data and return some data to the caller. Parameters are values that you pass to and from a procedure. There are many facets to parameters. Questions concerning parameters include:
There are six major mechanisms for passing data to and from a procedure, they are
You also have to worry about where you can pass parameters. Common places are
Finally, the amount of data has a direct bearing on where and how to pass it. The following sections take up these issues.
A parameter passed by value is just that - the caller passes a value to the procedure. Pass by value parameters are input only parameters. That is, you can pass them to a procedure but the procedure cannot return them. In HLLs, like Pascal, the idea of a pass by value parameter being an input only parameter makes a lot of sense. Given the Pascal procedure call:
CallProc(I);
If you pass I
by value, the CallProc
does not change the value of I
, regardless of what happens to the parameter
inside CallProc
.
Since you must pass a copy of the data to the procedure, you should only use this method for passing small objects like bytes, words, and double words. Passing arrays and strings by value is very inefficient (since you must create and pass a copy of the structure to the procedure).
To pass a parameter by reference, you must pass the address of a variable rather than its value. In other words, you must pass a pointer to the data. The procedure must dereference this pointer to access the data. Passing parameters by reference is useful when you must modify the actual parameter or when you pass large data structures between procedures.
Passing parameters by reference can produce some peculiar results. The following Pascal procedure provides an example of one problem you might encounter:
program main(input,output); var m:integer; procedure bletch(var i,j:integer); begin i := i+2; j := j-i; writeln(i,' ',j); end; . . . begin {main} m := 5; bletch(m,m); end.
This particular code sequence will print "00"
regardless of m
's value. This is because the parameters i
and
j
are pointers to the actual data and they both point at the same object.
Therefore, the statement j:=j-i;
always produces zero since i
and
j
refer to the same variable.
Pass by reference is usually less efficient than pass by value. You must dereference all pass by reference parameters on each access; this is slower than simply using a value. However, when passing a large data structure, pass by reference is faster because you do not have to copy a large data structure before calling the procedure.
Pass by value-returned (also known as value-result) combines features from both the pass by value and pass by reference mechanisms. You pass a value-returned parameter by address, just like pass by reference parameters. However, upon entry, the procedure makes a temporary copy of this parameter and uses the copy while the procedure is executing. When the procedure finishes, it copies the temporary copy back to the original parameter.
The Pascal code presented in the previous section would
operate properly with pass by value-returned parameters. Of course, when Bletch
returns to the calling code, m
could only contain one of the two values, but
while Bletch
is executing, i
and j
would contain
distinct values.
In some instances, pass by value-returned is more efficient than pass by reference, in others it is less efficient. If a procedure only references the parameter a couple of times, copying the parameter's data is expensive. On the other hand, if the procedure uses this parameter often, the procedure amortizes the fixed cost of copying the data over many inexpensive accesses to the local copy.
Pass by result is almost identical to pass by value-returned. You pass in a pointer to the desired object and the procedure uses a local copy of the variable and then stores the result through the pointer when returning. The only difference between pass by value-returned and pass by result is that when passing parameters by result you do not copy the data upon entering the procedure. Pass by result parameters are for returning values, not passing data to the procedure. Therefore, pass by result is slightly more efficient than pass by value-returned since you save the cost of copying the data into the local variable.
Pass by name is the parameter passing mechanism used by
macros, text equates, and the #define
macro facility in the C programming
language. This parameter passing mechanism uses textual substitution on the parameters.
Consider the following MASM macro:
PassByName macro Parameter1, Parameter2 mov ax, Parameter1 add ax, Parameter2 endm
If you have a macro invocation of the form:
PassByName bx, I
MASM emits the following code, substituting bx for Parameter1 and I for Parameter2:
mov ax, bx add ax, I
Some high level languages, such as ALGOL-68 and Panacea, support pass by name parameters. However, implementing pass by name using textual substitution in a compiled language (like ALGOL-68) is very difficult and inefficient. Basically, you would have to recompile a function everytime you call it. So compiled languages that support pass by name parameters generally use a different technique to pass those parameters. Consider the following Panacea procedure:
PassByName: procedure(name item:integer; var index:integer); begin PassByName; foreach index in 0..10 do item := 0; endfor; end PassByName;
Assume you call this routine with the statement
PassByName(A[i], i);
where A
is an array of integers having (at least)
the elements A[0]..A[10]. Were you to substitute the pass by name parameter item you would
obtain the following code:
begin PassByName; foreach index in 0..10 do A[I] := 0; (* Note that index and I are aliases *) endfor; end PassByName;
This code zeros out elements 0..10 of array A
.
High level languages like ALGOL-68 and Panacea compile pass by name parameters into functions that return the address of a given parameter. So in one respect, pass by name parameters are similar to pass by reference parameters insofar as you pass the address of an object. The major difference is that with pass by reference you compute the address of an object before calling a subroutine; with pass by name the subroutine itself calls some function to compute the address of the parameter.
So what difference does this make? Well, reconsider the
code above. Had you passed A[I]
by reference rather than by name, the calling
code would compute the address of A[I]
just before the call and passed in
this address. Inside the PassByName
procedure the variable item
would have always referred to a single address, not an address that changes along with
I
. With pass by name parameters, item
is really a function that
computes the address of the parameter into which the procedure stores the value zero. Such
a function might look like the following:
ItemThunk proc near mov bx, I shl bx, 1 lea bx, A[bx] ret ItemThunk endp
The compiled code inside the PassByName procedure might look something like the following:
; item := 0; call ItemThunk mov word ptr [bx], 0
Thunk is the historical term for these functions that
compute the address of a pass by name parameter. It is worth noting that most HLLs
supporting pass by name parameters do not call thunks directly (like the call
above). Generally, the caller passes the address of a thunk and the subroutine calls the
thunk indirectly. This allows the same sequence of instructions to call several different
thunks (corresponding to different calls to the subroutine).
11.5.6 Pass by Lazy-Evaluation
Pass by name is similar to pass by reference insofar as the procedure accesses the parameter using the address of the parameter. The primary difference between the two is that a caller directly passes the address on the stack when passing by reference, it passes the address of a function that computes the parameter's address when passing a parameter by name. The pass by lazy evaluation mechanism shares this same relationship with pass by value parameters - the caller passes the address of a function that computes the parameter's value if the first access to that parameter is a read operation.
Pass by lazy evaluation is a useful parameter passing technique if the cost of computing the parameter value is very high and the procedure may not use the value. Consider the following Panacea procedure header:
PassByEval: procedure(eval a:integer; eval b:integer; eval c:integer);
When you call the PassByEval
function it does
not evaluate the actual parameters and pass their values to the procedure. Instead, the
compiler generates thunks that will compute the value of the parameter at most one time.
If the first access to an eval
parameter is a read, the thunk will compute
the parameter's value and store that into a local variable. It will also set a flag so
that all future accesses will not call the thunk (since it has already computed the
parameter's value). If the first access to an eval
parameter is a write, then
the code sets the flag and future accesses within the same procedure activation will use
the written value and ignore the thunk.
Consider the PassByEval
procedure above.
Suppose it takes several minutes to compute the values for the a, b,
and c
parameters (these could be, for example, three different possible paths in a Chess game).
Perhaps the PassByEval
procedure only uses the value of one of these
parameters. Without pass by lazy evaluation, the calling code would have to spend the time
to compute all three parameters even though the procedure will only use one of the values.
With pass by lazy evaluation, however, the procedure will only spend the time computing
the value of the one parameter it needs. Lazy evaluation is a common technique artificial
intelligence (AI) and operating systems use to improve performance.
|
Table of Content | Chapter Eleven (Part 3) |
Chapter Eleven: Procedures and
Functions (Part 2)
27 SEP 1996