UMBC CMSC 313 -- Subprograms and the Stack Previous | Next


Subprograms and the Stack

So far the stack has been for storing the return address or saving the contents of registers before calling a procedure. There are some other things we can do with it: One simple answer covers the problems. Each procedure in execution is assigned a stack frame, a fixed-sized block of memory on the stack for parameters, return address, local variables and register storage. When a procedure is called, its stack frame is pushed onto the stack. The routine itself may push and pop temporary storage on the stack. Then,if this procedure is calls other procedures, the material for the first procedure will stay on the stack and each other procedure will adds its material to the stack. Then as every procedure terminates it must remove its material off the stack.

The bp (base pointer register points to a fixed location in the middle of the stack from of the currently executing procedure and the stack pointer sp points to the end of the procedure's temporary storage.

Because bp is an index register, the parts of the stack frame can be referenced relative to its contents by adding or subtracting a displacement. (Displacements are always referencing words!) These displacements are going to be the same for each and every use of a particular procedure. That way we we are use age as tht way we we are use age as the third parameter, it will always be [bp + 6]. Additional, the si and di registers can be used.

The calling procedure must push the parameters on the stack before making the procedure call. Then the called procedure must save and reset the bp, reserve space for thelocal variables, save the appropriate registers, and do its work. Then it must restore things in reverse order:

mySub:                         ;  Start of procedure  
         push  bp
         mov   bp, sp
         sub   sp, n           ; reserve n bytes of local storage
         push  reg1            ; save registers
         push  reg2

             ; do some processing 

         pop   reg2
         pop   reg1
         add   sp, n           ; just the opposite 
         mov   sp, bp
         pop   bp
         ret                   ; we are done.

This results in memory that looks like this:

When working with C functions, some of them have a variable number of parameters, where each time the function can have a different (variable) number of arguments. Printf is an example of this. It looks like:

printf(a1, ..., an); where n is the number of one-word parameters. They are pushed in the order of reverse order that they are encounter: an, ..., a1 After returning from such a call, there will be a statement like:
add sp, n; where n is two times the number of  2-byte parameters,
         ; four times the number of 4-byte parameters.

There are two kinds of parameters, call by value and call by reference. We have been using call by value when we do the push. Since the values are not retained, whatever changes that are made to them are lost. Notice that this is the default for C for everything except for arrays. Since the size of anything being pushed is in units of 16 bits or 32 bits, when the parameter is a character, it is promoted to 16-bits.

Both recusion and reentrancy are also solved with this method, because every call means that a separate frame are being manipulated. Each call becomes independent!

Procedures should be small, simple, and only do one thing. When possible, subprograms should not do any I/O. Then libraries can be built up and only the current outer driver has to worry about whether to use the keyboard, CRT, modem, disk file, etc. The inner procedures are independent of the I/O constraints that change with most programs.


Previous | Next

©2004, Gary L. Burt