• Aucun résultat trouvé

Parameter-Passing Mechanisms

Part 2 The Software Side: Disappointments and

6 Implications of Compiler and Systems Issues

6.4 Parameter-Passing Mechanisms

Every (conventional) programming language provides the facility of speci-fying functions or procedures or subroutines; these are subprograms with parameters. The purpose of the parameters is to make it possible for the operation embodied in the function to be applied to varying data. For exam-ple, when coding matrix multiplication, we want to be able to apply the subprogram to different matrices of different sizes. This is traditionally achieved through the use of parameters. It is useful to think of parameters as data structures that are explicitly designated for import and export.13

The use of parameters is tied intimately to the way in which parameters are passed. The three fundamentally different ways are call by value, call by refer-ence, and call by name. Other ways of passing parameters are variants or combinations of these. How parameters are passed depends on the programming language; parameter passing is defined as part of the language definition, and most programming languages do not support all three methods.

The three methods differ fundamentally in the way in which the para-meters of a function call are tied to the formal parapara-meters that are place-holders in the formulation of the function. Since this connection between actual and formal parameters can be very important for the run-time behav-ior of a program, we will briefly describe the three methods.

Call by value is generally considered the simplest of the three. It assumes that the actual parameter has a value and that the formal parameter corresponds to a memory location. Assuming these assumptions are satisfied, the actual parameter is evaluated and the resulting value is copied into the memory location(s) corre-sponding to the formal parameter. If an assumption is violated, call by value cannot be carried out (or results in an error, either at compile time or run time). The execution of a successful function call then proceeds as if the formal parameters were local variables of the function, except that they had been assigned initial values before the instructions in the body of the function were executed. As with all local variables, the formal parameters of a

13 Global parameters are sometimes used to circumvent the notion of restricting import and export strictly to parameters. The use of global variables tends to result in programs that are dif-ficult to debug and maintain. Unfortunately, today’s widely used programming languages do not force programmers to import and export explicitly, that is, through parameters. The strict prohibition of global variables would significantly reduce the debugging effort required and improve the maintainability of software.

 

C6730_C006.fm  Page 150  Friday, August 11, 2006  9:21 AM

Implications of Compiler and Systems Issues for Software 151 function are no longer accessible once execution of the body of the function has ended and control is returned to the calling program.14 An important aspect of call by value is that the space required for the formal parameters is to be considered additional space; therefore, it increases the space complexity of the resulting program. Since copying (of the values of the actual parameters to the locations of the formal parameters) is an operation that takes time, this also increases the time complexity.

Call by reference assumes that each actual parameter corresponds to a memory location. The corresponding formal parameter is then asso-ciated with this memory location for the duration of the execution of the function call. Thus, the use of a formal parameter in an instruction in the body of the function results in the use of the memory location that was assigned to the corresponding actual parameter. Since the memory space allocated to the actual parameters is also used by the formal parameters, call by reference tends to require less space.15 Call by name is essentially the invocation of a macro. It allows passing

functions as parameters.16 Since it is supported by very few pro-gramming languages and since it is rarely used even in those that do support it, we will not discuss it further.

Ordinarily, call by value is considered the safest way of passing para-meters, followed by call by reference, and (a distant third) call by name. This is in part because passing by value cannot affect anything in the calling program, except for the results that are reported back from the function. In contrast, the other ways can modify values in the calling program, since memory locations of the calling program (actual parameters) are manipu-lated by the function.

Typically, when designing an algorithm for a specific problem, one tends to ignore the need for a precise specification of the way in which this algo-rithm is tied into the overall program. For example, we may design an algorithm for the multiplication of two matrices or for the binary search of a sorted array. In doing so, the interface between the resulting algorithm, now encapsulated as a function, and the algorithm from which this function is called is usually of little concern. This changes as soon as this algorithm

14 Any space no longer accessible can be returned to the pool of available dynamic memory. This means that when invoking a function twice, there is no guarantee that the same space is allocated again.

15 Typically, one ignores the time required to establish the correspondence. This is reasonable since the number of parameters is negligible (compared with the space required by the data structures of a complex program). The issue for space is even less serious since no additional space is required (except when it comes to recursion, where the connection between actual and formal parameters must be recorded. To see the necessity for this, consider the Tower of Hanoi problem discussed in Chapter 3).

16 For example, it makes it possible to have a function Integral(f,a,b) that determines the definite integral of a given function f(x) between two points a and b in a way that permits the function f(x) to be a parameter.

 

C6730_C006.fm  Page 151  Friday, August 11, 2006  9:21 AM

152 A Programmer’s Companion to Algorithm Analysis is implemented as a program in a specific programming language. The way in which the function interacts with the calling program is crucial, as the following two examples indicate.

Consider the standard matrix multiplication algorithm, consisting of three nested loops, which assigns the product of the two [1:n,1:n] matrices A and B to the matrix C of the same type:

for i:=1 to n do for j:=1 to n do

{ C[i,j]:=0;

for k:=1 to n do

C[i,j] := C[i,j] + A[i,k]*B[k,j]

}

While this algorithm may not be the most efficient (see our discussion of a more efficient matrix multiplication algorithm in Chapter 3), it at least has the advantage of being correct. When encapsulating this algorithm into a function, for example, MatMult, the three matrices A, B, and C (as well as n) must be parameters:

function MatMult(A,B,C,n) { for i:=1 to n do

for j:=1 to n do { C[i,j]:=0;

for k:=1 to n do C[i,j] := C[i,j] + A[i,k]*B[k,j]

} }

Now we have to address the question of in what way these four parameters are to be passed. It is not difficult to see that n can be passed by value since the value, not the memory location, is what is relevant. How about the three matrices? Clearly, call by value does not work, since it would not allow the reporting back of the result (the matrix C) to the calling program. Recall that upon completed execution of the body, the local variables (including the formal parameters) are no longer accessible.17 Moreover, copying a [1:n,1:n]

matrix requires time proportional to n2. Thus, we may conclude that call by reference is appropriate for the three matrices.

Now consider the following entirely legitimate function call (essentially squaring the matrix X and assigning the result again to X), where X is a [1:n,1:n] matrix:

function MatMult(X,X,X,n)

17 Most programming languages do not allow the result of a function to be anything but an object of a simple type. Thus, the result of our function MatMult cannot be an array in these languages.

C6730_C006.fm  Page 152  Friday, August 11, 2006  9:21 AM

Implications of Compiler and Systems Issues for Software 153 Unfortunately, if we pass our three array parameters by reference, our code is wrong. To see this, consider the following matrix X:

X = , the square of X (X*X) is ,

but MatMult(X, X, X, n) yields .

Consequently, this approach of passing parameters does not work either.

It turns out that the only correct way of passing parameters to MatMult is to pass A and B (and n) by value and C by reference. Only in this way does the original algorithm yield a correct implementation of matrix multiplica-tion. This has important consequences for the time and space complexities of MatMult. Clearly, since A and B are passed by value, we must allocate additional space for these two matrices; this requires 2n2 locations. Note that C requires no additional space, since in call by reference the memory loca-tions of the actual parameter are used for the formal parameters. Moreover, the act of copying the actual parameters corresponding to the formal para-meters A and B requires time proportional to the number of elements that are copied, that is, 2n2. While the time complexity of the algorithm [O(n3)]

dominates the time complexity of copying, the space complexity is increased substantially, from O(1) to 2n2 + O(1).18

The second example is binary search. Assume we are given a sorted array A of size 1:n and a value x; BinSearch is to return an index i between 1 and n such that x = A[i], or else 0 if x is not present in A. Here is the code:

18 One can show that there is a superior way of doing this type of matrix multiplication:

function MatMult’(A,B,C,n)

{ declare a local [1:n,1:n] matrix D;

for i:=1 to n do

It follows that we can now pass all three array parameters by reference (the situation for n can be safely ignored since n is a single memory location; while both call by value and call by reference would work, one tends to go with the simpler approach, that is, call by value). In this way we save the 2n2 memory locations for A and B, since these two matrices are passed by reference, which does not require additional space, at the cost of allocating a local matrix D that requires n2 memory loca-tions. Thus, the total saving of the new version MatMult' (over MatMult, each with the stated ways of passing its parameters) is n2 memory locations and time proportional to n2.

1 2

154 A Programmer’s Companion to Algorithm Analysis function BinSearch(x,A,lo,hi)

{ allocate a local integer variable p;

while lo ≤ hi do { p:=(lo+hi)/2;

if x=A[p] then return(p)

else if x<A[p] then hi:=p-1 else lo:=p+1 };

return(0) }19

Again, the question arises of how to pass the four parameters when calling the function BinSearch(x,A,1,n). One can easily verify that the formal para-meters x, lo, and hi can be passed by value, since neither time nor space complexity are greatly affected by this. The situation changes dramatically when it comes to the array A. It is true that we could pass A by value — the resulting function would certainly be correct. However, in this case the space complexity is n, and the time complexity is O(n), plus the time com-plexity of the algorithm for binary search. Binary search (the algorithm) requires time O(log2(n)); thus, the time complexity of the code is O(n + log2(n)) or O(n). Consequently, the code (with the array A being passed by value) has a time complexity that is exponentially greater than that of the algorithm. This is truly awful — and clearly indicates that call by value is highly inappropriate. If we pass A by reference, there is no copying involved;

as a result, the space complexity is O(1) and the time complexity is exactly that of the algorithm, namely O(log2(n)).

We conclude that the treatment of parameters can have major implications on the behavior, and indeed on the correctness, of a program. This is an aspect of code that is generally not considered carefully by those designing algorithms. However, as these two simple examples indicate, this issue can make or break an implementation.