Memory Stack Register – Where Are Memory Addresses Located in x86?

memoryregisterstack

Suppose I have a very simple C program that just does this:

int i = 6;
int j = 4;
int k = 5;
int a = i + j + k;

Since i, j, and k are on the stack, they will be located relative to the stack pointer. I am told the compiler determines these relative locations; my guess is the compiler translates int a = i + j + k into "Add the values located at (stack pointer – 3x), (stack pointer – 2x), and (stack pointer – x) and push the result to the stack." Am I correct?

Best Answer

It depends upon the C compiler, as the specification leaves this up to the compiler implementer. (There are specific requirements, though -- such as if you take the address of a parameter -- which may further constrain the compiler implementer.)

A typical C function generates some "prologue" code, the "body" code, and some "epilogue" code. The prologue will allocate local variable space. In general, a compiler will NOT go through the process you outlined. So let's say we are talking about 16-bit x86 code and about this function in C:

int f( void ) {
    int i = 6;
    int j = 4;
    int k = 5;
    int a = i + j + k;
    return a;
}

Note that in the context I mentioned, the variables will be 2 bytes in size. The compiler will count the number of bytes required for all of the local variables. In this case, you have four of them and since they require 2 bytes each, the compiler "computes" that 8 bytes are required for all of this. (Also note that even if you include variable definitions inside of additional code blocks within the above C function, the C compiler will count ALL OF THEM AT ONCE. It doesn't just count those defined at the outer level.) So the compiler might generate the following prologue in assembly:

push bp
mov bp, sp
sub sp, 8

Usually, the BP register is used as a "frame pointer" for the current context of the function. BP is also known as the pointer to the "activation frame." So the C compiler needs two instructions to save the old activation frame pointer on the stack and to then initialize it to point to the current one for this invocation of the function.

The third instruction is simple and allocates ALL of the necessary space for the 8 bytes needed for ALL of the local variables. It simply moves the stack pointer over the needed 8 bytes. BP will still point at the base of this activation frame. But the SP now is at the other side of that, so additional pushes and calls won't write over the local variables.

The C compiler will also internally assign some offset value for each of the local variables. Perhaps something like IVAR=0, JVAR=2, KVAR=4, and AVAR=6. Now, the C compiler needs to create some BODY code. Because you set these local variable values, something like this (completely un-optimized):

mov IVAR[BP], 6
mov JVAR[BP], 4
mov KVAR[BP], 5
mov ax, IVAR[BP]
add ax, JVAR[BP]
add ax, KVAR[BP]

Note that in this 16-bit context, it's also common for the return value of a function to be placed into AX, if it fits there. In this case, it does. So the answer is already in the right place at this time. So now an epilogue is required:

mov sp, bp
pop bp
ret

And that's it.

Now, an optimizer will do a GREAT DEAL to improve the above code in this case. It will probably completely remove ALL of the local variables, as they aren't needed at all. It can pre-compute the entire result as 15 and just do the following:

mov ax, 15
ret

No need to manage the activation frame, at all. Just return the value. (But then you wouldn't get any idea about how the local variables might be managed.)

Hope that helps a little.

Related Topic