Parameter Passing Notes

Document last modified: 

Overview

Call-by-value and call-by-reference parameters both have merits in given applications and are commonly supported by high level languages. The meaning here is that normally used in C++. Call-by-value passes a copy of the value for the actual parameter via the stack from the caller to the callee. Call-by-reference differs in that the offset of the actual parameter is passed via the stack from the caller to the callee. The implication for the programmer is relatively straightforward and is summarized by the following examples.

Indirection Discussion

Indirection is necessary for implementing parameter passing via the stack. It is useful to first relate our usual notion of indirection to assembly language and the machine's architecture. C++ and Pascal support indirection using pointer variables that 1) hold an address that references 2) a variable, hence the indirection. The Intel processor uses registers (Bx, Si, Di, and Bp for 16-bit or any 32-bit) as pointers, these registers can hold an address used for the indirection. The following three examples are identical with variables i, an integer, and bx, a pointer to an integer. In the diagram at right, BX holds the address of variable i.

In the following Assembler program, we can examine the offsets of the variables and determine precisely what is the contents of eBx register. The instruction

Mov  eBx, Offset i
copies the offset of i, which is 32-bit 00000000416 to eBx.
C++, Pascal, Assembler Indirection Examples
C++ version                        

#include <iostream.h>             
                                        
void main ( void )                 
{                                      
     int     i;
     int     *ebx;                   
                                       
     i = 5;                      
     ebx = &i;       // Point to i
     cout << *ebx;   // Prints 5
}
Assembler version

.data
 00000000 	h	dd	3
 00000004	i	dd	5
 00000008	j	dd	9
.code
Main    Proc   Near

	Mov  eBx, Offset i    ;; Point to i, eBx=0000004
	Mov  eAx, [eBx]       ;; Get i value indirectly
	Call PutDec           ;; Prints 5
	Mov  eAx, 0
	Mov  eAx, [eBx+4]     ;; Get j value indirectly
	Call PutDec           ;; Prints 9

       	Push     0            ;; Stop    
       	Call     ExitProcess
Main   	Endp
        End     Main
Indirection to Access Parameters - By enclosing in square brackets, [], registers such as eBx, eSi, eDi and other 32-bit registers act as pointers to reference memory. Using 16-bit variables, Bx and Si can point to the data segement, Di a pointer to the extra segment, while Bp can be a pointer to the stack segment. In the above example Bx or Si could be used but not Bp since the variable i is in the data segment and Bp references the stack segment.

In the following assembly program examples, notice the use of eBp as a pointer to access the parameters on the stack. Suppose program fragments of:
 
Indirection to Access Stack Parameters C++, Pascal, Assembly 
C++ version

int p( int A, int b)
{
    return A + B;
}

void main( void ) {
     cout << p( 9, 7 );
}
Assembly version                        Stack after Mov eBp, eSp
     Push       7                ;             _________
     Push       9                ; Push  7    |    7    |  <- eBp+12  B
     Call       p                ;            |_________|
     Add        eSp, 8           ; Push  9    |    9    |  <- eBp+8   A
     Call       PutDec           ;            |_________|
                                 ; Call  p    | Ret Addr|  <- eBp+4
p    proc      near              ;            |_________|
     Push      eBp               ; Push  eBp  | Old eBp |  <- eBp+0 = eSp
     Mov       eBp, eSp          ;            |_________|
     Mov       eAx, [eBp+8]      ; eAx = 9       
     Add       eAx, [eBp+12]     ; eAx = 16
     Pop       eBp
     Ret       
p    Endp
Notice that after Mov e Bp, eSP the eBp register can be used to access parameters by adding an appropriate displacement to e Bp. For example, the instruction:

               Mov  eAx, [eBp+8]
adds 8 to the offset in eBp to indirectly access the stack location holding the A parameter. Because any calls made to this function would place the actual parameters at the same relative stack location, [eBp+8] will always correspond to the formal A parameter. This follows the protocol used by C++ in calling functions and passing value parameters.

General Rules for C++ Functions

Using functions requires following specific rules or protocol by both the caller and the callee. In general, the rules can be summarized by the following:
 
Caller Callee
  1. Save any registers and flags.
  2. Push parameters on the stack in right to left order.
  3. Call the function.
  4. Remove parameters from stack by adding to eSp number bytes pushed.
  5. Restore any registers and flags.
  1. Save eBp and any segment registers.
  2. Point eBp to parameters on stack.
  3. Access the parameters on the stack.
  4. Modify Al, Ax, eAx or eDx:eAx to return a result.
  5. Restore eBp and any segment registers.
  6. Return.

 
General Rules for Calling Functions
Assembly version                            
     Caller                                    Callee     
                                                 p  proc      near      
1.   Push      eDx                             1.   Push      eBp      
     PushfD                                    2.   Mov       eBp, eSp
2.   Push      7      ; B                      3.   Mov       eAx, [eBp+12]  ; B=7   
     Push      9      ; A                      4.   Mul       [eBp+8]        ; A=9
3.   Call      p                               5.   Pop       eBp
4.   Add       eSp, 8 ; Remove parameters      6.   Ret
5.   PopfD                                       p  Endp
     Pop       eDx    
Stack after Mov eBp, eSp
      _________   
     |   eDx   |
     |_________|
     |  Flags  |
     |_________|
B    |    7    |  <- eBp+12
     |_________|
A    |    9    |  <- eBp+8
     |_________|
     | Ret Addr|  <- eBp+4
     |_________|
     | Old eBp |  <- eBp+0 = eSp
     |_________|

Call-by-Value versus Call-by-Reference

The following figure illustrates the difference between value (left figure) and reference (right figure) passing in the program examples that follow later. In value passing the function does not have access to the original parameter, in reference passing the reference allows indirect access to the caller's parameters.
 
Call by Value                                                           Call by Reference 
Value

#include <iostream.h>
int X, A, B, C, Highest;

int Max (int Q, int R) 
{
   if (Q > R)
      return Q;
   else return R; 
}

void main(void) {
   A=2; B=3; C=8;
   X = Max( B, C );
   Highest = Max( A, X );
   cout << Highest << endl;          
}
Reference

#include <iostream.h>
int X, A, B, C, Highest;

int Max (int &Q, int &R) 
{
   if (Q > R)
      return Q;
   else return R; 
}

void main(void) {
   A=2; B=3; C=8;
   X = Max( B, C );
   Highest = Max( A, X );
   cout << Highest << endl;          
}

Call-by-value Discussion

Requires the value of the actual parameter to be pushed onto the stack by the calling routine. The called function uses the value directly from the stack. The called function cannot alter parameters because it lacks a reference (i.e. the data segment offset of the actual parameter is unknown).
 
Caller - The caller's responsibility is to:
  1. Push the value parameters onto the stack in right to left order prior to the function call.
  2. Call the function.
  3. Remove the parameters from the stack by adding to eSp the number of parameter bytes.
Callee - The callee's responsibility is to:
  1. Preserve the segment registers (Ds, Cs, Es, and SS) and eBp, all others can be altered.
  2. Access the parameters on the stack through eBp.
  3. Return using the Ret instruction.

A more complete example of call-by-value implementation is given below using C++ calling methods.
 

Call-by-value C++
C++ version

#include <iostream.h>
int X, A, B, Y, Highest;

int Max (int Q, int R) 
{
  if (Q > R)
     return Q;
  else return R; 
}

void main(void) {
   A=2; B=3; Y=8;
   X = Max( A, B );
   Highest = Max( X, Y );
   cout << Highest << endl;
}
Call-by-value Assembly
.data       
     X          dd   ?
     Y          dd   8
     B          dd   3
     A          dd   2
     Highest    dd   ?
.code

Main Proc   Near     ;; int Max( int Q, int R) {
     Push   B        ;; X=Max(A, B)
     Push   A          
     Call   Max
     Add    eSp, 8   ;; Remove A and B
     Mov    X, eAx

     Push   Y        ;; Highest=Max(X,Y)
     Push   X          
     Call   Max
     Add    eSp, 8   ;;Remove X and Y    
     Mov    Highest, eAx 

     Call   PutDec

     Push   0        ;; Stop    
     Call   ExitProcess
Main Endp







;; int Max( int Q, int R)
;;             - Find maximum of two int values
;;  Arguments  - Q, R int Values     
;;  Returns    - eAx contains maximum Q & R on return
;;  Registers Altered - eAx, eBx, eSi            

R     Equ   dword ptr[eBp+12];; Rightmost parameter 
Q     Equ   dword ptr[eBp+8] ;; Leftmost parameter

Max   Proc  Near          ;; Note Near procedure

      Push  eBp           ;; Save Caller's Frame Ptr
      Mov   eBp, eSp      ;; New Frame Ptr for Max

;;      ___________       Assuming that the call was
;;     |           |      Z = Max( A, B ); the stack 
;;     |___________|      appears at left after the
;; R   |   B = 3   | <- eBp+12   instruction sequence:
;;     |___________|                                
;; Q   |   A = 2   | <- eBp+8    Push    eBp      
;;     |___________|             Mov     eBp, eSp    
;;     |Return Addr| <- eBp+4                      
;;     |___________|
;;     | Old eBp   | <- eBp = eSp
;;     |___________|
                           ;; {
        Mov     eAx, R     ;;  
Ifa:    Cmp     Q, eAx     ;;  if (Q > R)
        Jg      Thena
        Jmp     Elsea  
 Thena: Mov     eAx, Q     ;;    eAx = Q;
        Jmp     EndIfa
 Elsea: Mov     eAx, R     ;;  else eAx = R;
EndIfa:                    ;; } 

        Pop     eBp        ;; Restore Frame Pointer
        Ret                ;; Return maximum in eAx 
Max    Endp                 
       End           Main

Call-by-reference Discussion

The protocol is nearly identical for call-by-value but rather than pushing values onto the stack, references to the parameters (offsets) are pushed. The protocol requires the data segment offset of the actual parameter to be pushed onto the stack by the calling routine. The called function then accesses the location of the actual parameter using the data segment offset. Since the parameter's location in memory is now accessible by the called function, it can be directly referenced and altered. The responsibility of the caller and callee is nearly identical for value and reference parameter passing, except that offsets are used rather than values to access parameters.
 
Caller - The caller's responsibility is to:
  1. Push the reference parameters (offsets) onto the stack in right to left order prior to the function call.
  2. Call the function.
  3. Remove the parameters from the stack by adding to eSp the number of parameter bytes.
Callee - The callee's responsibility is to:
  1. Preserve the segment registers (Ds, Cs, Es, and SS) and eBp, all others can be altered.
  2. Access the parameters on the stack using indirection.
  3. Return using the Ret instruction.

A more complete example of call-by-reference implementation is given below using C++ calling methods.
 

Call-by-reference Pascal and C++
C++ version

#include <iostream.h>
int X, A, B, Y, Highest;

int Max (int &Q, int &R) 
{
  if (Q > R)
     return Q;
  else return R; 
}

void main(void) {
   A=2; B=3; Y=8;
   X = Max( A, B );
   Highest = Max( X, Y );
   cout << Highest << endl;
}                          
Call-by-Reference Assembly 
.data       
00000000     X          dd   ?
00000004     Y          dd   8
00000008     B          dd   3
0000000C     A          dd   2
00000010     Highest    dd   ?
.code

Main Proc   Near     ;; int Max( int Q, int R) {
     Push   offset B ;; X=Max(A, B)
     Push   offset A          
     Call   Max
     Add    eSp, 8   ;; Remove A and B
     Mov    X, eAx

     Push   offset Y ;; Highest=Max(X,Y)
     Push   offset X          
     Call   Max
     Add    eSp, 8   ;; Remove X and Y    
     Mov    Highest, eAx 

     Call   PutDec

     Push   0        ;; Stop    
     Call   ExitProcess
Main Endp

;; int Max( int &Q, int &R)
;;             - Find maximum of two int references
;;  Arguments  - Q, R int reference
;;  Returns    - eAx contains maximum Q & R on return
;;  Registers Altered - eAx, eBx, eSi            

Max   Proc  Near          ;; Note Near procedure

      Push  eBp           ;; Save Caller's Frame Ptr
      Mov   eBp, eSp      ;; New Frame Ptr for Max

;;      ___________       Assuming that the call was
;;     |           |      Z = Max( A, B ); the stack 
;;     |___________|      appears at left after the
;; R   |&B=00000008| <- eBp+12   instruction sequence:
;;     |___________|                                
;; Q   |&A=0000000C| <- eBp+8    Push    eBp      
;;     |___________|             Mov     eBp, eSp    
;;     |Return Addr| <- eBp+4                      
;;     |___________|
;;     | Old eBp   | <- eBp = eSp
;;     |___________|
                           ;; {
        Mov     eBx, [eBp+12]
        Mov	eAx, [eBx]  
Ifa:    Mov     eSi, [eBp+8]
        Cmp     [eSi], eAx ;;  if (Q > R)
        Jg      Thena
        Jmp     Elsea  
 Thena: Mov     eAx, [eSi] ;;    eAx = Q;
        Jmp     EndIfa
 Elsea: Mov     eAx, [eBx] ;;  else eAx = R;
EndIfa:                    ;; } 
        Pop     eBp        ;; Restore Frame Pointer
        Ret                ;; Return maximum in eAx 
Max    Endp                 
       End           Main                

Dynamic Variables Discussion

In C++ procedures, dynamic variables are generally termed local variables, which are allocated each time the procedure is entered and deallocated on each exit. To allocate storage for a local or dynamic variable, the stack pointer is decremented, once for a byte variable, twice for a word variable, 200 times for an array of 100 words, etc. Deallocation is by restoring the stack pointer, by Mov e Sp, eBp. References to the dynamic variable are still relative to eBp though the displacement is normally negative rather than positive. Generally, dynamic variables use requires all the same steps used when entering or exiting a procedure, summarized below:
     Entering
     1)   Push eBp        
     2)   Mov  eBp, eSp
     3)   Sub  eSp, #          # is the number of bytes to allocate for dynamic variables
     4)   Push Registers       Save any registers

     Exiting
     4)   Pop  Registers       Restore any registers
     3)   Mov  eSp, eBp        Restore eSp which deallocates dynamic variables
     2)   Pop  eBp
     1)   Ret

Dynamic Variables C++ and Assembler

int a = 5, b = 6;

void main(void) {
 cout << addup(a, b);	
}
.data
	a	dd	5
	b	dd	6
.code
main	proc	near
	push	offset b
	push	a
	call	addup
	add	eSp, 8  
	call	PutDec 
	push	0
	call	ExitProcess
main	endp
 int addup(int x, int &y)
 {	int sum;
	sum = y;
	sum = sum + x;
	return sum;
 }
 addup  PROC 	NEAR
	push	eBp
	mov	eBp, eSp
	sub	eSp, 4		; int sum
        mov	eBx, [eBp+12]	; sum = y;
	mov	eAx, [eBx]
	add	eAx, [eBp+8]	; sum = sum + x
	mov	[eBp-4], eAx
	mov	eAx, [eBp-4]	; return sum
	mov	eSp, eBp
	pop	eBp
        ret
 addup  ENDP

Dynamic Variables C++
C++ version

int  eAx;
#include <iostream.h>
int Factorial( int N ) {    
  int i;
  eAx = 1;   
  for (i=1; i<=N; i++) 
      eAx = eAx * i;
  return eAx;
}

void main (void) {
   cout << Factorial(6);
}
Dynamic Variables Assembly
     PutDec proto

.code

Main Proc   Near 	; void main() {

     Push   6		
     Call   Factorial 	 
     Add    eSp, 4            

     Call   PutDec	;  cout << Factorial(6);

     Push   0            
     Call   ExitProcess
Main Endp		; }
;; int Factorial (int N); // Calculate N! 
;;  Parameters        - N positive integer value 
;;  Returns           - eAx return factorial value
;;  Registers Altered - eAx
;;      ___________           Assuming the call
;; N   |     6     | <- eBp+8 was Factorial( 6 );
;;     |___________|          the stack appears  
;;     |Return Addr| <- eBp+4 after the instruction
;;     |___________|          sequence of:  
;;     | Old eBp   | <- eBp+0   
;;     |___________|                              
;; i   |    ??     | <- eBp-4         Push    Bp   
;;     |___________|                  Mov     Bp, Sp
;; eDx |    ??     | <- eBp-8         Sub     Sp, 2
;;     |___________|                  Push    Dx 
;; Flag|    ??     | <- eBp-12 = eSp  PushfD
;;     |___________|
Factorial Proc Near   ;;int Factorial(int N)

  N       Equ  dword ptr [eBp+8]
  i       Equ  dword ptr [eBp-4]

          Push eBp
          Mov  eBp, eSp  ;;  __________________
          Sub  eSp, 4    ;; | Allocate i       |
                         ;; | one double word  |
          Push eDx       ;; |__________________| 
          PushfD               

          Mov  eAx, 1    ;; Factorial = 1
          Mov  i, 1
Fora:     Mov  eDx, i
          Cmp  eDx, N    ;; for(i=1;i<=N;N++)
	  Jbe  Doa
          Jmp  EndFora   ;;  Factorial*=i
     Doa:                ;;   
          Mul  i              
          Inc  i
          Jmp  Fora          
EndFora:                       
          PopfD                
          Pop  eDx        
                        ;;  ____________
          Mov  eSp, eBp ;; |Restore eSp |
          Pop  eBp      ;; |deallocate i|
          Ret           ;; |____________|     
Factorial Endp        
          End  Main

Double Indirection - The swap function illustrates an appropriate use of reference parameters, that is when the parameter requires a side-effect. It also illustrates that call-by-reference parameters have an access cost of two indirections. To access the right-most variable B in the following example requires:

  1. Mov eBx, [eBp+12]    Access the reference parameter on the stack, the offset of B is 00008004.
  2. Mov e Ax, [eBx]        Access the variable referenced, the variable B value is 6.
Call-by-Reference Swap Example
C++

void swap( int &L, int &R) {   
   int T;
   T = L;
   L = R;
   R = T;
}

void main(void) {
     int A = 5, B = 6;
     swap( A, B);
}


 Stack after Sub eSp, 2
   _______ 
R | 8004  | eBp+12   B
L | 8000  | eBp+8    A
  | ret   | eBp+4
  |Old eBp| eBp+0
T | ????  | eBp-4
 
 Data definitions
   ____  Offset
  || 00008000  A  DD  5
  || 00008004  B  DD  6







Assembly

.data       
     A          dd   5
     B          dd   6
.code

swap  Proc  Near
      Push  eBp
      Mov   eBP, eSp
      Sub   eSp, 4       ; int T    
      
      Mov   eBx, [eBp+8] ; T = L
      Mov   eAx, [eBx]
      Mov   [eBp-4], eAx

      Mov   eBx, [eBp+8] ; L = R
      Mov   eSi, [eBp+12]
      Mov   eAx, [eSi]
      Mov   [eBx], eAx

      Mov   eAx, [eBp-4] ; R = T
      Mov   eSi, [eBp+12]
      Mov   [eSi], eAx

      Mov   eSp, eBp
      Pop   eBp
      Ret    
swap  Endp
main  Proc  Near

      Push  Offset B
      Push  Offset A
      Call  swap
      Add   eSp, 8

      Push  0            
      Call  ExitProcess    
main  Endp
      End   main
Document last modified: