neilobremski
Experienced Member
The SS register stores the Stack Segment and the logical stack itself grows down. When you PUSH a register, its value is added at SS:SP and SP is decremented by 2. [SUP]1[/SUP] A procedure's local variables, then, are allocated below SP by the amount of bytes required to store them.
Let's start by looking at a simple C function that merely declares some local variables, manipulates them, and then returns ...
Firstly the declaration of local variables does not issue individual machine instructions per variable. Those first two lines declare to the C compiler that there are three char variables (8-bit) and four short integers (16-bit). For space, however, the machine code generated is just a subtraction to SP (remember the stack grows downward!) relative to the amount of space needed for the local variables. [SUP]2[/SUP]
A second thing that needs to happen is the stack's base pointer BP is set to the current stack pointer SP (after being pushed to the stack itself of course; for backup). The reason for this is that the machine code will need to reference the local variables by a constant offset and SP is unsuited for this because it's always changing as pushes and pops occur. By making the offset relative to BP it can remain the same across the entire procedure.
Let's look at how Microsoft C 5.10 produces the assembler with /Ox optimizations enabled (I've added most of the Line # comments):
That last part where the function ends may look somewhat mysterious. Why isn't SP incremented by 14 since it was decremented by that much earlier? The answer is that it is replaced to its original value by copying BP which would be the same as adding 14. However, this way of doing it could be "safer" because any stack corruption that occurred within the function or one of its calls would be erased.
Notice again that all of the local variables are simply negative offsets from the stack base pointer BP. These are consistent offsets because BP is not changed during the course of this function. And if another procedure is called, it will store BP on the stack and restore it before returning so that it remains unchanged for our purposes.
Footnotes:
[SUP]1[/SUP] Only 16-bit values are ever pushed onto the stack with either PUSH or PUSHF (push flags).
[SUP]2[/SUP] Technically this means you'd need 11 bytes but compilers will often word-align the locals so that every char occupies its own 16-bit space! For the example, then, there are 14 bytes "allocated".
Let's start by looking at a simple C function that merely declares some local variables, manipulates them, and then returns ...
Code:
void locals(void)
{
char x, y, z;
short a, b, c, d;
a = b + c * d;
x += y - z;
}
Firstly the declaration of local variables does not issue individual machine instructions per variable. Those first two lines declare to the C compiler that there are three char variables (8-bit) and four short integers (16-bit). For space, however, the machine code generated is just a subtraction to SP (remember the stack grows downward!) relative to the amount of space needed for the local variables. [SUP]2[/SUP]
A second thing that needs to happen is the stack's base pointer BP is set to the current stack pointer SP (after being pushed to the stack itself of course; for backup). The reason for this is that the machine code will need to reference the local variables by a constant offset and SP is unsuited for this because it's always changing as pushes and pops occur. By making the offset relative to BP it can remain the same across the entire procedure.
Let's look at how Microsoft C 5.10 produces the assembler with /Ox optimizations enabled (I've added most of the Line # comments):
Code:
; Line 1 void locals(void)
_locals PROC NEAR
; Line 2 {
push bp
mov bp,sp
; Line 3 char x, y, z;
; Line 4 short a, b, c, d;
sub sp,14
; x = -10
; y = -12
; z = -14
; a = -2
; b = -4
; c = -6
; d = -8
; Line 5 a = b + c * d;
mov ax,WORD PTR [bp-6] ;c
imul WORD PTR [bp-8] ;d
add ax,WORD PTR [bp-4] ;b
mov WORD PTR [bp-2],ax ;a
; Line 6 x += y - z;
mov al,BYTE PTR [bp-12] ;y
sub al,BYTE PTR [bp-14] ;z
add BYTE PTR [bp-10],al ;x
; Line 7 }
mov sp,bp
pop bp
ret
nop
_locals ENDP
That last part where the function ends may look somewhat mysterious. Why isn't SP incremented by 14 since it was decremented by that much earlier? The answer is that it is replaced to its original value by copying BP which would be the same as adding 14. However, this way of doing it could be "safer" because any stack corruption that occurred within the function or one of its calls would be erased.
Notice again that all of the local variables are simply negative offsets from the stack base pointer BP. These are consistent offsets because BP is not changed during the course of this function. And if another procedure is called, it will store BP on the stack and restore it before returning so that it remains unchanged for our purposes.
Footnotes:
[SUP]1[/SUP] Only 16-bit values are ever pushed onto the stack with either PUSH or PUSHF (push flags).
[SUP]2[/SUP] Technically this means you'd need 11 bytes but compilers will often word-align the locals so that every char occupies its own 16-bit space! For the example, then, there are 14 bytes "allocated".