5.7 Parameters


5.7 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. In straight assembly language, passing parameters can be a real chore. Fortunately, HLA provides a high level language–like syntax for procedure declarations and for procedure calls involving parameters. This section will present HLA's high level parameter syntax. Later sections in this chapter will deal with the low level mechanisms for passing parameters in pure assembly code.

The first thing to consider when discussing parameters is how we pass them to a procedure. If you are familiar with Pascal or C/C++, you've probably seen two ways to pass parameters: pass by value and pass by reference. HLA certainly supports these two parameter passing mechanisms. However, HLA also supports pass by value/result, pass by result, pass by name, and pass by lazy evaluation. Of course, HLA is assembly language, so it is possible to pass parameters in HLA using any scheme you can dream up (at least, any scheme that is possible at all on the CPU). However, HLA provides special high level syntax for pass by value, reference, value/result, result, name, and lazy evaluation.

Because pass by value/result, result, name, and lazy evaluation are somewhat advanced, this chapter will not deal with those parameter passing mechanisms. If you're interested in learning more about these parameter passing schemes, see the HLA Reference Manual or check out the electronic versions of this text on the accompanying CD-ROM.

Another concern you will face when dealing with parameters is where you pass them. There are a lot of different places to pass parameters; in this section, because we're using HLA's high level syntax for declaring and calling procedures, we'll wind up passing procedure parameters on the stack. You don't really need to concern yourself with the details because HLA abstracts them away for you; however, do keep in mind that procedure calls and procedure parameters make use of the stack. Therefore, whatever you push on the stack immediately before a procedure call is not going to be on the top of the stack upon entry into the procedure.

5.7.1 Pass by Value

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 values through them. In HLA the idea of a pass by value parameter being an input only parameter makes a lot of sense. Given the HLA procedure call:

 CallProc(I); 

If you pass I by value, then CallProc does not change the value of I, regardless of what happens to the parameter inside CallProc.

Because 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 large arrays and records by value is very inefficient (because you must create and pass a copy of the object to the procedure).

HLA, like Pascal and C/C++, passes parameters by value unless you specify otherwise. Here's what a typical function looks like with a single pass by value parameter:

      procedure PrintNSpaces( N:uns32 );      begin PrintNSpaces;           push( ecx );           mov( N, ecx );           repeat                stdout.put( ' ' ); // Print 1 of N spaces.                dec( ecx );        // Count off N spaces.           until( ecx = 0 );           pop( ecx );      end PrintNSpaces; 

The parameter N in PrintNSpaces is known as a formal parameter. Anywhere the name N appears in the body of the procedure the program references the value passed through N by the caller.

The calling sequence for PrintNSpaces can be any of the following:

 PrintNSpaces( constant ); PrintNSpaces( reg32 ); PrintNSpaces( uns32_variable ); 

Here are some concrete examples of calls to PrintNSpaces:

 PrintNSpaces( 40 ); PrintNSpaces( EAX ); PrintNSpaces( SpacesToPrint ); 

The parameter in the calls to PrintNSpaces is known as an actual parameter. In the examples above, 40, EAX, and SpacesToPrint are the actual parameters.

Note that pass by value parameters behave exactly like local variables you declare in the var section with the single exception that the procedure's caller initializes these local variables before it passes control to the procedure.

HLA uses positional parameter notation just like most high level languages. Therefore, if you need to pass more than one parameter, HLA will associate the actual parameters with the formal parameters by their position in the parameter list. The following PrintNChars procedure demonstrates a simple procedure that has two parameters.

      procedure PrintNChars( N:uns32; c:char );      begin PrintNChars;           push( ecx );           mov( N, ecx );           repeat                stdout.put( c );      // Print 1 of N characters.                dec( ecx );           // Count off N characters.           until( ecx = 0 );           pop( ecx );      end PrintNChars; 

The following is an invocation of the PrintNChars procedure that will print 20 asterisk characters:

 PrintNChars( 20, '*' ); 

Note that HLA uses semicolons to separate the formal parameters in the procedure declaration, and it uses commas to separate the actual parameters in the procedure invocation (Pascal programmers should be comfortable with this notation). Also note that each HLA formal parameter declaration takes the following form:

 parameter_identifier : type_identifier 

In particular, note that the parameter type has to be an identifier. None of the following are legal parameter declarations because the data type is not a single identifier:

 PtrVar: pointer to uns32 ArrayVar: uns32[10] recordVar: record i:int32; u:uns32; endrecord DynArray: array.dArray( uns32, 2 ) 

However, don't get the impression that you cannot pass pointer, array, record, or dynamic array variables as parameters. The trick is to declare a data type for each of these types in the type section. Then you can use a single identifier as the type in the parameter declaration. The following code fragment demonstrates how to do this with the four data types in the preceding list:

 type      uPtr:     pointer to uns32;      uArray10: uns32[10];      recType:  record i:int32; u:uns32; endrecord      dType:    array.dArray( uns32, 2 );      procedure FancyParms      (           PtrVar:  uPtr;           ArrayVar: uArray10;           recordVar:recType;           DynArray: dtype      );      begin FancyParms;           .           .           .      end FancyParms; 

By default, HLA assumes that you intend to pass a parameter by value. HLA also lets you explicitly state that a parameter is a value parameter by prefacing the formal parameter declaration with the val keyword. The following is a version of the PrintNSpaces procedure that explicitly states that N is a pass by value parameter:

      procedure PrintNSpaces( val N:uns32 );      begin PrintNSpaces;           push( ecx );           mov( N, ecx );           repeat                stdout.put( ' ' ); // Print 1 of N spaces.                dec( ecx );        // Count off N spaces.           until( ecx = 0 );           pop( ecx );      end PrintNSpaces; 

Explicitly stating that a parameter is a pass by value parameter is a good idea if you have multiple parameters in the same procedure declaration that use different passing mechanisms.

When you pass a parameter by value and call the procedure using the HLA high level language syntax, HLA will automatically generate code that will make a copy of the actual parameter's value and copy this data into the local storage for that parameter (i.e., the formal parameter). For small objects pass by value is probably the most efficient way to pass a parameter. For large objects, however, HLA must generate code that copies each and every byte of the actual parameter into the formal parameter. For large arrays and records this can be a very expensive operation.[6] Unless you have specific semantic concerns that require you to pass a large array or record by value, you should use pass by reference or some other parameter passing mechanism for arrays and records.

When passing parameters to a procedure, HLA checks the type of each actual parameter and compares this type to the corresponding formal parameter. If the types do not agree, HLA then checks to see if either the actual or formal parameter is a byte, word, or double word object and the other parameter is one, two, or four bytes in length (respectively). If the actual parameter does not satisfy either of these conditions, HLA reports a parameter type mismatch error. If, for some reason, you need to pass a parameter to a procedure using a different type than the procedure calls for, you can always use the HLA type coercion operator to override the type of the actual parameter.

5.7.2 Pass by Reference

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.

To declare a pass by reference parameter you must preface the formal parameter declaration with the var keyword. The following code fragment demonstrates this:

      procedure UsePassByReference( var PBRvar: int32 );      begin UsePassByReference;           .           .           .      end UsePassByReference; 

Calling a procedure with a pass by reference parameter uses the same syntax as pass by value except that the parameter has to be a memory location; it cannot be a constant or a register. Furthermore, the type of the memory location must exactly match the type of the formal parameter. The following are legal calls to the procedure above (assuming i32 is an int32 variable):

 UsePassByReference( i32 ); UsePassByReference( (type int32 [ebx] ) ); 

The following are all illegal UsePassbyReference invocations (assumption: charVar is of type char):

 UsePassByReference( 40 );           // Constants are illegal. UsePassByReference( EAX );          // Bare registers are illegal. UsePassByReference( charVar );      // Actual parameter type must match                                     // the formal parameter type. 

Unlike the high level languages Pascal and C++, HLA does not completely hide the fact that you are passing a pointer rather than a value. In a procedure invocation, HLA will automatically compute the address of a variable and pass that address to the procedure. Within the procedure itself, however, you cannot treat the variable like a value parameter (as you could in most high level languages). Instead, you treat the parameter as a double word variable containing a pointer to the specified data. You must explicitly dereference this pointer when accessing the parameter's value. The example appearing in Listing 5-8 provides a simple demonstration of this.

Listing 5-8: Accessing Pass by Reference Parameters.

start example
 program PassByRefDemo; #include( "stdlib.hhf" ); var      i: int32;      j: int32;      procedure pbr( var a:int32; var b:int32 );      const           aa: text := "(type int32 [ebx])";           bb: text := "(type int32 [ebx])";      begin pbr;           push( eax );           push( ebx );      // Need to use EBX to dereference a and b.           // a = -1;           mov( a, ebx );    // Get ptr to the "a" variable.           mov( -1, aa );    // Store -1 into the "a" parameter.           // b = -2;           mov( b, ebx );    // Get ptr to the "b" variable.           mov( -2, bb );    // Store -2 into the "b" parameter.           // Print the sum of a+b.           // Note that ebx currently contains a pointer to "b".           mov( bb, eax );           mov( a, ebx );    // Get ptr to "a" variable.           add( aa, eax );           stdout.put( "a+b=", (type int32 eax), nl );      end pbr; begin PassByRefDemo;      // Give i and j some initial values so      // we can see that pass by reference will      // overwrite these values.      mov( 50, i );      mov( 25, j );      // Call pbr passing i and j by reference      pbr( i, j );      // Display the results returned by pbr.      stdout.put      (           "i= ", i, nl,           "j= ", j, nl      ); end PassByRefDemo; 
end example

Passing parameters by reference can produce some peculiar results in some rare circumstances. Consider the pbr procedure in Listing 5-8. Were you to modify the call in the main program to be "pbr(i,i)" rather than "pbr(i,j);", the program would produce the following non-intuitive output:

 a+b=-4 i=  -2; j=  25; 

The reason this code displays "a+b=-4" rather than the expected "a+b=-3" is because the "pbr(i,i);" call passes the same actual parameter for a and b. As a result, the a and b reference parameters both contain a pointer to the same memory location — that of the variable i. In this case, a and b are aliases of one another. Therefore, when the code stores -2 at the location pointed at by b, it overwrites the 1 stored earlier at the location pointed at by a. When the program fetches the value pointed at by a and b to compute their sum, both a and b point at the same value, which is 2. Summing 2 + 2 produces the 4 result that the program displays. This non-intuitive behavior is possible any time you encounter aliases in a program. Passing the same variable as two different reference parameters probably isn't very common. But you could also create an alias if a procedure references a global variable and you pass that same global variable by reference to the procedure (this is a good example of yet one more reason why you should avoid referencing global variables in a procedure).

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 because it typically requires at least two instructions. However, when passing a large data structure, pass by reference is faster because you do not have to copy the large data structure before calling the procedure. Of course, you'd probably need to access elements of that large data structure (e.g., an array) using a pointer, so very little efficiency is lost when you pass large arrays by reference.

[6]Note to C/C++ programmers: HLA does not automatically pass arrays by reference. If you specify an array type as a formal parameter, HLA will emit code that makes a copy of each and every byte of that array when you call the associated procedure.




The Art of Assembly Language
The Art of Assembly Language
ISBN: 1593272073
EAN: 2147483647
Year: 2005
Pages: 246
Authors: Randall Hyde

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net