Passing Parameters on the Runtime Stack


Passing by address

Consider this procedure from Pascal

     procedure Swap (var First, Second : integer);
          var Temp: integer;
     begin
          Temp := First;
          First := Second;
          Second := Temp;
     end;

or the similar procedure from C

     void function Swap (int* First, int*  Second);
          int Temp;
     begin
          Temp := *First;
          *First := *Second;
          *Second := Temp;
     end;

in both cases, the parameters are passed by address. This means that the address of the variable from the main program is passed to the procedure instead of the value of the variable. This is not a liability, since if you have the address of the variable, then its value is obtained easily. The advantage of having the address is that the procedure is allowed to change the value of the variable in the main program.

Back to Index


Passing by value

Consider another example from Pascal,

     procedure PrintResult (Total, Count: integer; var Avg: real);
     begin
          println( The total is  ,Total);
          println( There were ', Count,   numbers entered');
          Avg := Total / Count;
          println( The average is  ,Avg);
     end;

and from C,

     void function PrintResult (int Total, int Count, float* Avg);
     begin
          printf( The total is %d\n ,Total);
          printf( There were %d numbers entered\n', Count);
          Avg = (float) Total / (float) Count;
          printf( The average is %f\n ,Avg);
     end;

In these examples, the first two variables are passed by value. Since the procedure doesn't change either of these variables, then only the values need to be passed. The last parameter is passed by address, since its value is changing.

Back to Index


Where are variables placed when they are passed?

It is possible to call a procedure from one file when the actual procedure is in another file. For example, consider the procedures println and printf from the above procedures. So, where can the parameters be placed so that the caller can place them, and the procedure can retrieve them?

There are three possibilities

Global variables are not feasible, since it would require that every time you call a procedure you would have to call it with the same global variable. This would be a nuisance. Registers are not feasible, since there aren't enough of them. Sooooooooo, we are left with the stack. Each executable file has one stack and every module in the program has access to the stack. It is also very big.

Back to Index


How is the stack used?

It is important to remember how the stack works. It is possible to push a value onto the stack, and to pop a value off the stack. This is the mechanism used for placing information on the stack and removing it when the data is no longer needed

It is also important to understand that a stack is just an array, so if the index of an element in the stack is known, then it is possible to retrieve information from the stack without actually popping the information off the stack. In other words, besides pushing and popping, we are also allowed to peek into the stack. To take this a step further, it is possible to change the contents of an element of the stack if you know the element's index. Perhaps we can call this posting. The only register that we will use for peeking and posting will be the BP register. Any memory reference that uses BP automatically references the stack segment instead of the data segment.

Action of pushing

PUSH NUM

This is what actually happens when pushing NUM onto the stack (warning: you can't actually use SP in this fashion)
ADD SP,2
MOV [SP],NUM

Action of popping

POP NUM

This is what actually happens when popping NUM off the stack (warning: you can't actually use SP in this fashion)
MOV NUM,[SP]
SUB SP,2

Action of peeking

Suppose that BP+4 is an address that points to an element on the stack. Then to store that value into NUM
MOV NUM,[BP+4]
Notice that the stack pointer is not changed. We are only retrieving a value from the stack.

Action of posting

Suppose that BP+4  is an address that points to an element on the stack. Then the following will change the value on the stack to the value in NUM. Notice that any memory reference using BP, automatically accesses the stack segment.
MOV [BP+4],NUM
Notice that the stack pointer is not changed. We are only changing a value on the stack.

Back to Index


How is an array referenced?

It is important to remember how an array is referenced in assembler. First the address of the array is loaded into a register, then the [ ] are used to reference the array. Suppose that BX has the starting address of an array and that the array contains bytes, then the following references into the array are possible:

     [BX]           ;first element of the array
     [BX+1]         ;second element of the array
     [BX+2]         ;third element of the array
 

Suppose the array contained words, then the references would be

     [BX]           ;first element of the array
     [BX+2]         ;second element of the array
     [BX+4]         ;third element of the array

Back to Index


How are variables passed?

A variable is passed in one of two ways

pass by value: push the value of the variable onto the stack.
pass by address: push the address of the variable onto the stack.

Examples of passing

Pass by value
Register:
PUSH AX
CALL FUN
Variable NUM (NUM must be a word):
PUSH NUM
CALL FUN
Constant NUM (cannot push immediate values):
MOV AX,NUM
PUSH AX
CALL FUN
Pass by address
Register
can't pass a register by address
Variable NUM
MOVAX,offset NUM
PUSH AX
CALL FUN
Constant
can't pass a constant by address

Back to Index


C calling convention

The C calling convention states that the parameters are pushed onto the stack in reverse order. That means that the last parameter is pushed first and the first parametersis pushed last. This means that in the procedure, the first parameter will be the parameter closest to the top of the stack.

It is also necessary to remove the parameters from the stack after the procedure returns. Do this by popping all the values that were placed onto the stack before the call.

Calling the Swap procedure

    
     ;Calling Swap (Num1, Num2);

     LEA  AX,NUM2
     PUSH AX
     LEA  AX,NUM1
     PUSH AX
     CALL SWAP
     POP  AX
     POP  AX

Calling the PrintResult procedure

     
     ;Calling PrintResult (Balance,30, AvgBalance);

     LEA  AX,AvgBalance
     PUSH AX
     MOV  AX,30
     PUSH AX
     PUSH Balance
     CALL PrintResult
     POP  AX
     POP  AX
     POP  AX

Back to Index


Retrieving parameters inside a procedure

The C calling convention uses the BP register to remember where the parameters are placed on the stack. You can be sure of the following

     BP+4      Location of parameter 1
     BP+6      Location of parameter 2
     BP+8      Location of parameter 3
     etc

Also, if the procedure uses local variables, then they will be located at

     BP-2      Location of first local variable
     BP-4      Location of second local variable
     BP-6      Location of third local variable
     etc

However, this assurance doesn't come for free. The programmer writing the procedure must be sure that the BP is set up correctly. The first thing to realize, is that every procedure uses the BP in the same way, so if the BP is to be changed, it must be restored to its original value before the procedure returns, so that the calling procedure will be able to find its parameters and local variables.

A picture of the stack and data segments

Consider the following Data Segment and partial Code Segment for passing parameters on the stack

	.DATA
Num	DW	12h
Value	DW	6h
NumArr	DB	34h,12h,8h,9h,44h,23h

	.CODE
	...
	...
	mov	ax,offset NumArr
	push	ax
	mov	ax,offset Num
	push	ax
	mov	ax,Value
	push	ax
	call	Test
	...
	...

Test	proc	near
	push	bp
	mov	bp,sp
	...
	...

The Stack and Data segments would appear as follows. Note that the addresses are arbitrary, I chose these numbers just as examples of how the memory might be allocated.
Stack Segment
Relative to BP Address Data
BP E90A old
E90B BP
BP+2 E90C old
E90D IP
BP+4 E90E 06
E90F 00
BP+6 E910 00
E911 00
BP+8 E912 04
E913 00
Data Segment
Symbol Address Data
Num 0000 12
0001 00
Value 0002 06
0003 00
NumArr 0004 34
0005 12
0006 08
0007 09
0008 44
0009 23

Retrieving the value of a parameter

After the call, [BP+4] holds the value of the parameter that was passed by value.

In order to add this value to the AX register, the following command would be used inside the Test procedure

ADD AX,[BP+4]

In terms of the memory, this is saying

ADD AX,SS:[E90E]

Treating the stack as an array, this says to add the value from the array, 0006h, to AX. Notice that the memory that is being referenced is the SS, since any memory reference using BP addresses the SS.


After the call, [BP+6] holds the address of the parameter that was passed by address.

Accessing the value for this parameter requires two steps

MOV BX,[BP+6]
ADD AX,[BX]

In terms of the memory, this says

MOV BX,SS:[E910]
ADD AX,DS:[0000]

Step one treats the stack as an array, and moves the value 0000h into BX. This value is the address of Num in the data segment. Step two treats the data segment as an array and adds the value 0012h to AX. Again, notice that any memory reference using BP addresses the stack, whereas the memory reference using BX references the data segment.


As a third example, by loading the address from BP+8 into BX, and loading the value at BP+4 into CX, it is possible to access all the values in the array, NumArr:

	MOV  BX,[BP+8]
	MOV  CX,[BP+4]
TOP:	ADD  AX,[BX]
	INC  BX
	LOOP TOP

Here is what happens if I unroll the loop and see what each reference looks like:

	MOV  BX,SS:[E912]	;address of NumArr
	MOV  CX,SS:[E90E]	;size of NumArr
	Add  AX,DS:[0004]	;add 34h to AX
	INC  BX			;BX becomes 0005
	Add  AX,DS:[0005]	;add 12h to AX
	INC  BX			;BX becomes 0006
	Add  AX,DS:[0006]	;add 8h to AX
	INC  BX			;BX becomes 0007
	Add  AX,DS:[0007]	;add 9h to AX
	INC  BX			;BX becomes 0008
	Add  AX,DS:[0008]	;add 44h to AX
	INC  BX			;BX becomes 0009
	Add  AX,DS:[0009]	;add 23h to AX
	INC  BX

So, by passing just two parameters on the stack, it is possible to access an entire array in the data segment.

Changing the value of a parameter

Using the same situations as above, the following steps would change the value of a parameter

After the call, [BP+4] holds the value of the parameter that was passed by value.

In order to add the AX register to this value, the following command would be used inside the Test procedure

ADD [bp+4],AX

In terms of the memory, this is saying

ADD SS:[E90E],AX

Treating the stack as an array, this says to add the AX register to the value from the array. Notice that the memory that is being referenced is the SS, since any memory reference using BP addresses the SS, so that the value in the data segment for Value doesn't change, only the value on the stack changes.


After the call, [BP+6] holds the address of the parameter that was passed by address.

Accessing the value for this parameter requires two steps

MOV BX,[BP+6]
ADD [BX],AX

In terms of the memory, this says

MOV BX,SS:[E910]
ADD DS:[0000],AX

Step one treats the stack as an array, and moves the value 0000h into BX. This value is the address of Num in the data segment. Step two treats the data segment as an array and adds AX to the value stored there. So, by using pass-by-address it is possible for a procedure to change a variable in the data segment without referencing the name of of that variable. Again, notice that any memory reference using BP addresses the stack, whereas the memory reference using BX references the data segment.


As a third example, by loading the address from BP+8 into BX, and loading the value at BP+4 into CX, it is possible to access all the values in the array, NumArr:

	MOV  BX,[BP+8]
	MOV  CX,[BP+4]
TOP:	ADD  [BX],AX
	INC  BX
	LOOP TOP

Here is what happens if I unroll the loop and see what each reference looks like:

	MOV  BX,SS:[E912]	;address of NumArr
	MOV  CX,SS:[E90E]	;size of NumArr
	Add  DS:[0004],AX	;add AX to 34h
	INC  BX			;BX becomes 0005
	Add  DS:[0005],AX	;add AX to 12h
	INC  BX			;BX becomes 0006
	Add  DS:[0006],AX	;add AX to 8h
	INC  BX			;BX becomes 0007
	Add  DS:[0007],AX	;add AX to 9h
	INC  BX			;BX becomes 0008
	Add  DS:[0008],AX	;add AX to 44h
	INC  BX			;BX becomes 0009
	Add  DS:[0009],AX	;add AX to 23h
	INC  BX

So, by passing just two parameters on the stack, it is possible to access an entire array in the data segment.

Back to Index


Local Variables

Local parameters are also allocated on the stack. They are also referenced with regard to the BP.

	BP-2		Address for the first local parameter		
	BP-4		Address for the second local parameter
	BP-6		Address for the third local parameter

	[BP-2]		Value of the first local parameter		
	[BP-4]		Value of the second local parameter
	[BP-6]		Value of the third local parameter

To allocate space for locals,  just subtract the number of required bytes from the stack pointer: SUB SP,6

	

          PUSH BP           ;save caller's BP
          MOV  BP,SP        ;set up current BP
          SUB  SP,6         ;allocate three local variables, each is a word
          ...
          ...
          ...
          MOV  SP,BP        ;restore SP and remove locals from the stack
          POP  BP           ;restore caller's BP
          RET
 

Procedure Swap

Two parameters passed by address, NUM1 and NUM2. One local variable TEMP. Please not that using a local variable, TEMP, is not necessary and that the code would be much simpler without it. However, it does illustrate how to allocate and reference a local variable.

SWAP      PROC      NEAR
          PUSH BP
          MOV  BP,SP
          SUB  SP,2           ;allocate a word on the stack for TEMP
          PUSH AX             ;save caller's AX
          PUSH SI             ;save caller's SI, since it will be used
          PUSH DI             ;same for DI
          MOV  SI,[BP+4]      ;get address of NUM1
          MOV  AX,[SI]        ;get value of NUM1
          MOV  [BP-2],AX      ;store value of NUM1 into TEMP
          MOV  DI,[BP+6]      ;address of NUM2
          MOV  AX,[DI]        ;value of NUM2
          MOV  [SI],AX        ;store value of NUM2 into NUM1
          MOV  AX,[BP-2]      ;value of TEMP
          MOV  [DI],AX        ;store value of TEMP into NUM2
          POP  DI             ;restore caller's registers
          POP  SI
          POP  AX
          MOV  SP,BP	      ;dealocate the local variable TEMP
          POP  BP             ;restore the caller's BP
          RET
SWAP      ENDP
 

And you thought that swapping was easy?

Back to Index