|
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