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" on page 576). 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.
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" on page 606). Fortunately, there are better parameter passing schemes for passing data in memory so you do not need to seriously consider this scheme.