|
Table of Content | Chapter Eleven (Part 4) |
| CHAPTER ELEVEN: PROCEDURES AND FUNCTIONS (Part 3) |
| 11.5.7 -
Passing Parameters in Registers 11.5.8 - Passing Parameters in Global Variables |
Having touched on how to pass parameters to a procedure the next thing to discuss is where to pass parameters. Where you pass parameters depends to a great extent on the size and number of those parameters. If you are passing a small number of bytes to a procedure then the registers are an excellent place to pass parameters. The registers are an ideal place to pass value parameters to a procedure. If you are passing a single parameter to a procedure you should use the following registers for the accompanying data types:
Data Size Pass in this Register Byte: al Word: ax Double Word: dx:ax or eax (if 80386 or better)
This is
by no means
a hard and fast rule. If you find it
more convenient to pass 16 bit values in the si or bx register
by all means do so. However
most programmers use the registers above to pass parameters.
If you are passing several parameters to a procedure in the 80x86's registers you should probably use up the registers in the following order:
First Last
ax
dx
si
di
bx
cx
In general
you should avoid using bp
register. If you need more than six words
perhaps you should pass your values elsewhere.
The UCR Standard Library package provides several good
examples of procedures that pass parameters by value in the registers. Putc
which outputs an ASCII character code to the video display
expects an ASCII value in the al
register. Likewise
puti expects the value of a signed integer in the ax
register. As another example
consider the following putsi (put short
integer) routine that outputs the value in al as a signed integer:
putsi proc push ax ;Save AH's value. cbw ;Sign extend AL -> AX. puti ;Let puti do the real work. pop ax ;Restore AH. ret putsi endp
The other four parameter passing mechanisms (pass by
reference
value-returned
result
and name) generally require that you pass a pointer to
the desired object (or to a thunk in the case of pass by name). When passing such
parameters in registers
you have to consider whether you're passing an offset or a full
segmented address. Sixteen bit offsets can be passed in any of the 80x86's general purpose
16 bit registers. si
di
and bx are the best place
to pass an offset since you'll probably need to load it into one of these registers anyway[4]. You can pass 32 bit segmented addresses dx:ax
like other double word parameters. However
you can also pass them in ds:bx
ds:si
ds:di
es:bx
es:si
or es:di and be
able to use them without copying into a segment register.
The UCR Stdlib routine puts
which prints a
string to the video display
is a good example of a subroutine that uses pass by
reference. It wants the address of a string in the es:di register pair. It
passes the parameter in this fashion
not because it modifies the parameter
but because
strings are rather long and passing them some other way would be inefficient. As another
example
consider the following strfill(str
c) that copies the character c
(passed by value in al) to each character position in str
(passed by reference in es:di) up to a zero terminating byte:
; strfill- copies value in al to the string pointed at by es:di ; up to a zero terminating byte. byp textequ <byte ptr> strfill proc pushf ;Save direction flag. cld ;To increment D with STOS. push di ;Save because it's changed. jmp sfStart sfLoop: stosb ;es:[di] := al di := di + 1; sfStart: cmp byp es:[di] 0 ;Done yet? jne sfLoop pop di ;Restore di. popf ;Restore direction flag. ret strfill endp
When passing parameters by value-returned or by result to a subroutine you could pass in the address in a register. Inside the procedure you would copy the value pointed at by this register to a local variable (value-returned only). Just before the procedure returns to the caller it could store the final result back to the address in the register.
The following code requires two parameters. The first is a
pass by value-returned parameter and the subroutine expects the address of the actual
parameter in bx. The second is a pass by result parameter whose address is in
si. This routine increments the pass by value-result parameter and stores the
previous result in the pass by result parameter:
; CopyAndInc- BX contains the address of a variable. This routine ; copies that variable to the location specified in SI ; and then increments the variable BX points at. ; Note: AX and CX hold the local copies of these ; parameters during execution. CopyAndInc proc push ax ;Preserve AX across call. push cx ;Preserve CX across call. mov ax [bx] ;Get local copy of 1st parameter. mov cx ax ;Store into 2nd parm's local var. inc ax ;Increment 1st parameter. mov [si] cx ;Store away pass by result parm. mov [bx] ax ;Store away pass by value/ret parm. pop cx ;Restore CX's value. pop ax ;Restore AX's value. ret CopyAndInc endp
To make the call CopyAndInc(I J) you would use code like the following:
lea bx I lea si J call CopyAndInc
This is
of course
a trivial example whose implementation
is very inefficient. Nevertheless
it shows how to pass value-returned and result
parameters in the 80x86's registers. If you are willing to trade a little space for some
speed
there is another way to achieve the same results as pass by value-returned or pass
by result when passing parameters in registers. Consider the following implementation of CopyAndInc:
CopyAndInc proc mov cx ax ;Make a copy of the 1st parameter inc ax ; then increment it by one. ret CopyAndInc endp
To make the CopyAndInc(I J) call as before you would use the following 80x86 code:
mov ax I call CopyAndInc mov I ax mov J cx
Note that this code does not pass any addresses at all; yet
it has the same semantics (that is
performs the same operations) as the previous version.
Both versions increment I and store the pre-incremented version into J.
Clearly the latter version is faster
although your program will be slightly larger if
there are many calls to CopyAndInc in your program (six or more).
You can pass a parameter by name or by lazy evaluation in a
register by simply loading that register with the address of the thunk to call. Consider
the Panacea PassByName procedure (see "Pass by Name"). One
implementation of this procedure could be the following:
;PassByName- Expects a pass by reference parameter index ; passed in si and a pass by name parameter item ; passed in dx (the thunk returns the address in bx). PassByName proc push ax ;Preserve AX across call mov word ptr [si] 0 ;Index := 0; ForLoop: cmp word ptr [si] 10 ;For loop ends at ten. jg ForDone call dx ;Call thunk item. mov word ptr [bx] 0 ;Store zero into item. inc word ptr [si] ;Index := Index + 1; jmp ForLoop ForDone: pop ax ;Restore AX. ret ;All Done! PassByName endp
You might call this routine with code that looks like the following:
lea si I lea dx Thunk_A call PassByName . . . Thunk_A proc mov bx I shl bx 1 lea bx A[bx] ret Thunk_A endp
The advantage to this scheme
over the one presented in the
earlier section
is that you can call different thunks
not just the ItemThunk
routine appearing in the earlier example.
11.5.8 Passing Parameters in Global Variables
Once you run out of registers the only other (reasonable) alternative you have is main memory. One of the easiest places to pass parameters is in global variables in the data segment. The following code provides an example:
mov ax xxxx ;Pass this parameter by value mov Value1Proc1 ax mov ax offset yyyy ;Pass this parameter by ref mov word ptr Ref1Proc1 ax mov ax seg yyyy mov word ptr Ref1Proc1+2 ax call ThisProc . . . ThisProc proc near push es push ax push bx les bx Ref1Proc1 ;Get address of ref parm. mov ax Value1Proc1 ;Get value parameter mov es:[bx] ax ;Store into loc pointed at by pop bx ; the ref parameter. pop ax pop es ret ThisProc endp
Passing parameters in global locations is inelegant and inefficient. Furthermore if you use global variables in this fashion to pass parameters the subroutines you write cannot use recursion (see "Recursion"). Fortunately there are better parameter passing schemes for passing data in memory so you do not need to seriously consider this scheme.
[4] This does not apply to thunks. You may pass the address of a thunk in any 16 bit register. Of course on an 80386 or later processor you can use any of the 80386's 32-bit registers to hold an address.
|
Table of Content | Chapter Eleven (Part 4) |
Chapter Eleven: Procedures and
Functions (Part 3)
27 SEP 1996