With the C programming language and UNIX operating system (as with all
languages and operating systems) there are certian conventions that are
adhered to when using the stack - though there are variations between architectures
and implementations. These conventions revolve around the subroutine call
and return.
Say we have two subroutines, A and B as follows:
A{
B(1,2,3);
}
B(X,Y,Z){
.....
}
This is an example of a typical calling convention:
Calling routine A does:
Push the arguments onto the stack in reverse
order . They are pushed on in reverse order so that the first argument
can easily be found (think about what I mean by this ... do you know?)
Push the address of the next statement (return
address) onto the stack and branch to the called routine (B). Most architectures
have a CALL instruction to do this as one step.
The Called routine (B) does:
record value of bottom of current stack frame
(the current location of the stack pointer) - possibly in a special register
called the frame pointer. Save the value of the calling stack frame
onto the stack for later restoration. Some architectures have a special
machine instruction to do this all in one swoop. In the 68000 is is called
link. It does:
*SP = FP
FP = SP
SP --
In the 80x86 architecture, the one way of doing this follows the previous convention precisely. In this architecture, the frame pointer is called BP (base pointer).
PUSH BP
MOV BP,SP
saves the registers it expects to modify during
the execution of the subroutine (they are saved onto the stack). In particular, on the 80x86, the FLAGS register is important to save (performed by the PUSHF instruction.
executes (and finds arguments on stack relative
to FP).
restores the registers
Pops the entire frame down to the FP (that is
what an FP is for!)., and resets the FP to be the old FP. This essentially
removes all traces of the subroutine call from the stack. Again - some
architectures have an instruction to do this. On the 68000 it is called
unlink. It does:
SP = FP
FP = *FP
SP++
On the 80x86, it is:
POP BP
Pops the return address off the stack and into
the PC (most architectures have a RTS (return from subroutine instruction
to do this))
Now that we are back in the calling routine (A),
the routine:
pops all the arguments it originally placed on
the stack.
During the execution of B, the stack would look
like the following:
If you want to see this in action (and you should want to) you
can compile a trivial C program with one subroutine calling another empty
subroutine. Make sure the called subroutine has an argument or two, as
well as a local variable or two.. No need to do anything with them. Compile
with the -S option to produce assembly rather than an executable. Then
look at the assembly to see what is going on! Can you figure out what is
happening?
Here is a program you can try this on:
a(int x)
{int y;}
main(){
a(17);
}
Call is x.c and go cc
-S x.c
This will produce x.s, the assembly equivalent of x.c. If you want to see the ouput in various assembly languages, you must give the appropriate flags to the compiler to generate code for a different architecture.