tags:

views:

390

answers:

6

I'm worried that I am misunderstanding something about stack behavior in C.

Suppose that I have the following code:

int main (int argc, const char * argv[]) 
{
    int a = 20, b = 25;
    {
     int temp1;
     printf("&temp1 is %ld\n" , &temp1);
    }

    {
     int temp2;
     printf("&temp2 is %ld\n" , &temp2);
    }
    return 0;
}

Why am I not getting the same address in both printouts? I am getting that temp2 is one int away from temp1, as if temp1 was never recycled.

My expectation is for the stack to contain 20, and 25. Then have temp1 on top, then have it removed, then have temp2 on top, then have it removed.

I am using gcc on Mac OS X.

Note that I am using the -O0 flag for compiling without optimizations.

Tho those wondering about the background for this question: I am preparing teaching materials on C, and I am trying to show the students that they should not only avoid returning pointers to automatic variables from functions, but also to avoid taking the address of variables from nested blocks and dereferencing them outside. I was trying to demonstrate how this causes problems, and couldn't get the screenshot.

A: 

This is completely dependent on the compiler and how it is configured.

Daniel Earwicker
+4  A: 

There is no guarantee what address stack objects will receive regardless of the order they are declared.

The compiler can happily reorder the creation and duration of stack variables providing it does not affect the results of the function.

Andrew Grant
That's a good answer. I assumed (not sure why) that if I hit -O0 it will actually give me a certain guarantee (based on the "textbook behavior").
Uri
+18  A: 

The compiler is completely within its rights not to optimize temp1 and temp2 into the same location. It has been many years since compilers generated code for one stack operation at a time; these days the whole stack frame is laid out at one go. (A few years back a colleague and I figured out a particularly clever way to do this.) Naive stack layout probably puts each variable in its own slot, even when, as in your example, their lifetimes don't overlap.

If you're curious, you might get different results with gcc -O1 or gcc -O2.

Norman Ramsey
That's a good answer. I assumed (not sure why) that if I hit -O0 it will actually give me a certain guarantee (based on the "textbook behavior").
Uri
Uri, with -O0, your observed behavior is exactly what I would have expected: Each variable gets its own spot, and the compiler doesn't bother trying to consolidate them. If the same were observed at higher optimization, I'd be disappointed.
Rob Kennedy
maybe try with -Os then
Johannes Schaub - litb
Yes, the compiler is free to do pretty much as it pleases. The stack layout -- or lack thereof, these variables could theoretically be put in static memory if the compiler can prove that there is case where the function is called recursively indirectly -- is completely up to the compiler.
jakobengblom2
+3  A: 

I believe the C standard just talks about the scope and lifetime of variables defined in a block. It makes no promises about how the variables interact with the stack or if a stack even exists.

sigjuice
+2  A: 
Alex B
How things get put into memory is an implementation detail that the compiler is supposed to hide and do how it likes with, not anything the standard deals with . The C standard is a rule book, and the compilers do anything they can within these rules.
jakobengblom2
+1  A: 

There is no standard that sets how variables are placed on the stack. What happens in the compiler is much more complicated. In your code, the compiler may even choose to completely ignore and suppress variables a and b.

During the many stages of the compiler, the code may be converted to it's SSA form, and all stack variables lose their addresses and meanings in this form (it may even make it harder for the debugger).

Stack space is very cheap, in the sense that the time to allocate either 2 or 20 variables is constant. Also, stack space is very dynamic for most function calls, since with the exception of a few functions (those nearer main() and thread-entry functions, with long-lived event loops or so), they tend to complete quickly. So, you just don't bother with them.

Juliano