Compiler optimization about elimination of pointer operation on inline function in C? - c

If this function Func1 is inlined,
inline int Func1 (int* a)
{
return *a + 1;
}
int main ()
{
int v = GetIntFromUserInput(); // Unknown at compile-time.
return Func1(&v);
}
Can I expect a smart compiler to eliminate the pointer operations? (&a and *a)
As I guess, the function will be transformed into something like this,
int main ()
{
int v = GetIntFromUserInput(); // Unknown at compile-time.
int* a = &v;
return *a + 1;
}
and finally,
int main ()
{
int v = GetIntFromUserInput(); // Unknown at compile-time.
return v + 1;
}
Pointer operations look easily being eliminated. But I heard that pointer operation is something special and cannot be optimized.

Yes the compiler, as said by Wallyk, is able to remove useless operations in this case.
However you must remember that when you specify a function signature something is lost in the translation from your problem domain to C. Consider the following function:
void transform(const double *xyz, // Source point
double *txyz, // Transformed points
const double *m, // 4x3 transformation matrix
int n) // Number of points to transform
{
for (int i=0; i<n; i++) {
txyz[0] = xyz[0]*m[0] + xyz[1]*m[3] + xyz[2]*m[6] + m[9];
txyz[1] = xyz[0]*m[1] + xyz[1]*m[4] + xyz[2]*m[7] + m[10];
txyz[2] = xyz[0]*m[2] + xyz[1]*m[5] + xyz[2]*m[8] + m[11];
txyz += 3; xyz += 3;
}
}
I think that the intent is clear, however the compiler must be paranoid and consider that the generated code must behave exactly as described by the C semantic even in cases that are of course not part of the original problem of transforming an array of points like:
txyz and xyz are pointing to the same memory address, or maybe they are pointing to adjacent doubles in memory
m is pointing inside the txyz area
This means that for the above function the C compiler is forced to assume that after each write to txyz any of xyz or m could change and so those values cannot be loaded in free order. The resulting code consequently will not be able to take advantage of parallel execution for example of the computations of the tree coordinates even if the CPU would allow to do so.
This case of aliasing was so common that C99 introduced a specific keyword to be able to tell the compiler that nothing so strange was intended. Putting the restrict keyword in the declaration of txyz and m reassures the compiler that the pointed-to memory is not accessible using other ways and the compiler is then allowed to generate better code.
However this "paranoid" behavior is still necessary for all operations to ensure correctness and so for example if you write code like
char *s = malloc(...);
char *t = malloc(...);
... use s and t ...
the compiler has no way to know that the two memory areas will be non-overlapping or, to say it better, there is no way to define a signature in the C language to express the concept that returned values from malloc are "non overlapping". This means that the paranoid compiler (unless some non-standard declarations are present for malloc and the compiler has a special handling for it) will think in the subsequent code that any write to something pointed by s will possibly overwrite data pointed by t (even when you're not getting past the size passed to malloc I mean ;-) ).
In your example case even a paranoid compiler is allowed to assume that
no one will know the address of a local variable unless getting it as a parameter
no unknown external code is executed between the reading and computation of addition
If both those points are lost then the compiler must think to strange possibilities; for example
int a = malloc(sizeof(int));
*a = 1;
printf("Hello, world.\n");
// Here *a could have been changed
This crazy thought is necessary because malloc knows the address of a; so it could have passed this information to printf, which after printing the string could use that address to change the content of the location. This seems clearly absurd and maybe the library function declaration could contain some special unportable trick, but it's necessary for correctness in general (imagine malloc and printf being two user defined functions instead of library ones).
What does all this blurb mean? That yes, in your case the compiler is allowed to optimize, but it's very easy to remove this possibility; for example
inline int Func1 (int* a) {
printf("pointed value is %i\n", *a);
return *a + 1;
}
int main () {
int v = GetIntFromUserInput(); // Assume input value is non-determinable.
printf("Address of v is %p\n", &v);
return Func1(&v);
}
is a simple variation of your code, but in this case the compiler cannot avoid assuming that the second printf call could have changed the pointed memory even if it's passed just the pointed value and not the address (because the first call to printf was passed the address and so the compiler must assume that potentially that function could have stored the address to use it later to alter the variable).
A very common misconception in C and C++ is that liberal use of the keyword const with pointers or (in C++) references will help the optimizer generating better code.
This is completely false:
In the declaration const char *s the nothing is said about that the pointed character is going to be constant; it's simply said that it is an error to change the pointed character using that pointer. In other words const in this case simply means that the pointer is "readonly" but doesn't tell that, for example, other pointers could be used to changed the very same memory pointed to by s.
It is legal in C (and C++) to "cast away" const-ness from a pointer (or reference) to constant. So the paranoid compiler must assume that even a function has been only handed a const int * the function could store that pointer and later can use it to change the memory pointed to.
The const keyword with pointers (and C++ references) is only meant as an aid for the programmer to avoid unintentional writing use of a pointer that was thought as being used only for reading. Once this check is performed then this const keyword is simply forgotten by the optimizer because it has no implications in the semantic of the language.
Sometimes you may find another silly use of the const keyword with parameters that tells that the value of the parameter cannot be changed; for example void foo(const int x).
This kind of use has no real philosophical meaning for the signature and simply puts some little annoyance on the implementation of the called function: a parameter is a copy of a value and caller shouldn't care if the called function is going to change that copy or not... the called function can still make a copy of the parameter and change that copy so nothing is gained anyway.
To recap... when the compiler sees
void foo(const int * const x);
must still assume that foo will potentially store away a copy of the passed pointer and that can use this copy to change the memory pointed to by x immediately or later when you call any other unknown function.
This level of paranoia is required because of how the language semantic is defined.
It is very important to understand this "aliasing" problem (there can be different ways to alter the same writable area of memory), especially with C++ where there is a common anti-pattern of passing around const references instead of values even when logically the function should accept a value. See this answer if you are also using C++.
All these are the reasons for which when dealing with pointers or references the optimizer has much less freedom than with local copies.

It is reasonable that it might occur. For example, gcc -O3 does so:
.globl main
.type main, #function
main:
pushl %ebp
movl %esp, %ebp
andl $-16, %esp
call GetIntFromUserInput
movl %ebp, %esp
popl %ebp
addl $1, %eax
ret
Notice that it takes the return value from the function, adds one, and returns.
Interestingly, it also compiled a Func1, probably since inline seems like it should have the meaning of static, but an external function (like GetIntFromUserInput) ought to be able to call it. If I add static (and leave inline), it does remove the function's code.

Related

How does a compiler know if something is allocated on the stack or heap?

How does a compiler know if something is allocated on the heap or stack, for instance if I made a variable in a function and returned the address of the variable, the compiler warns me that "function returns address of a local variable":
#include <stdio.h>
int* something() {
int z = 21;
return &z;
}
int main() {
int *d = something();
return 0;
}
I understand why this is a warning because when the function exits, the stack frame is no more and if you have a pointer to that memory and you change it's value you will cause a segmentation fault. What I wonder is how the compiler will know if that variable is allocating memory via. malloc, or how it can tell if it's a local variable on the stack?
A compiler builds a syntax tree from which it is able to analyze each part of the source code.
It builds a symbol table which associates to each symbol defined some information. This is required for many aspects:
finding undeclared identifiers
checking that types are convertible
so on
Once you have this symbol table it is quite easy to know if you are trying to return the address of a local variable since you end up having a structure like
ReturnStatement
+ UnaryOperator (&)
+ Identifier (z)
So the compiler can easily check if the identifier is a local stack variable or not.
Mind that this information could in theory propagate along assignments but in practice I don't think many compilers do it, for example if you do
int* something() {
int z = 21;
int* pz = &z;
return pz;
}
The warning goes away. With static code flow analysis you could be able to prove that pz could only refer to a local variable but in practice that doesn't happen.
The example in your question is really easy to figure out.
int* something() {
int z = 21;
return &z;
}
Look at the expression in the return statement. It takes the address of the identifier z.
Find out where z is declared. Oh, it is a local variable.
Not all cases will be as easy as this one and it's likely that you can trick the compiler into giving false positives or negatives if you write sufficiently weird code.
If you're interested in this kind of stuff, you might enjoy watching some of the talks given at CppCon'15 where static analysis of C++ code was a big deal. Some remarkable talks:
Bjarne Stroustrup: “Writing Good C++14”
Herb Sutter: “Writing Good C++14… By Default”
Neil MacIntosh: “Static Analysis and C++: More Than Lint”
The compiler knows what chunk of memory is holding the current stack. Every time a function is called it creates a new stack and moves the previous frame and stack pointers appropriately which effectively give it a beginning and endpoint for the current stack in memory. Checking to see if you're trying to return a pointer to memory that's about to get freed is relatively simple given that setup.
What I wonder is how the compiler will know if that variable is
allocating memory via. malloc, or how it can tell if it's a local
variable on the stack?
The compiler has to analyse all the code and generate machine code from it.
When functions need to be called, the compiler has to push the parameters on the stack (or reserve registers for them), update the stack pointer, look if there are local variables, initialize those on the stack too and update the stack pointer again.
So obviously the compiler knows about local variables being pushed on the stack.

C -- Modify const through aliased non-const pointer

Is it allowed in standard C for a function to modify an int given as const int * using an aliased int *? To put it another way, is the following code guaranteed to always return 42 and 1 in standard C?
#include <stdio.h>
void foo(const int *a, int *b)
{
printf("%d\n", *a);
*b = 1;
printf("%d\n", *a);
}
int main(void)
{
int a = 42;
foo(&a, &a);
return 0;
}
In your example code, you have an integer. You take a const pointer to it, and a non-const pointer to it. Modifying the integer via the non-const pointer is legal and well-defined, of course.
Since both pointers are pointers to integers, and the const pointer need not point to a const object, then the compiler should expect that the value read from the const pointer could have changed, and is required to reload the value.
Note that this would not be the case if you had used the restrict keyword, because it specifies that a pointer argument does not alias any other pointer argument, so then the compiler could optimise the reload away.
Yes and yes. Your program is defined.
The fact that you point to an non-const int variable with a pointer to a const int, doesn't make that variable const and may be still modified trough a pointer to an int or by using the original variable label.
Yes you can do this (if you know you can get away with it).
One reason you may not be able to get away with it, is if the destination memory you are writing to is in a read-only protected areas (such as constant data) then you will get an access violation. For example any const's at compile time that end up in read-only data sections of the executable. Most platform support protecting it from being written to at runtime.
Basically don't do it.
There are other issues with your example that probably don't make it the best demonstration. Such as needing a reload of *a in the 2nd printf the compiler may optimize it out! (it knows 'a' did not change, it know's 'a' points to a const, therefore, it does not need to reload memory by preforming a memory load for the 2nd '*a' expression, it can reuse the value it probably has in a register from the 1st time it loaded '*a'). Now if you add in a memory barrier between, then your example has a chance of working better.
https://en.wikipedia.org/wiki/Memory_barrier
GCC ? asm volatile ("" : : : "memory"); // might work before 2nd printf
But the principal for the actual question you asked, yes you can do it if you know what you are doing about other stuff like that.
Yes, it is guaranteed to always print 42 and 1.
const int *a means the value pointed to is a constant for pointer a.
Try dereferencing from a (*a = 10;) in the function and you will get an error.
The pointer a however is not constant. You can do a = b for example.
b can point to the same address as a and/or modify the value, as you did in your example.
Would you declare b pointer's value to be constant (const int *b), you would receive an error.
I try to memorize like this:
const int *a - a points to an object of type int, which it is not allowed to modify (any other pointer to that object can do what it wants, depends on its declaration/definition).

Saving code space by altering a function call in C

I am calling a function that returns a variable through a pointer parameter. I do not care about the return value of this parameter nor do I want to make a dummy variable to pass to the function. For a simple example's sake, let's say the function is as follows and I don't care about, nor want to make a dummy variable for parameter "d".
void foo(int a, int b, int* c, int* d)
{
*c = a+b;
*d = a+b+*c;
}
I understand that a NULL pointer is in theory a pointer to a location that is not the address of any object or function. Would it be correct to pass NULL into "d" in this function if NULL was defined as the following? Or is this going to change whatever is at the 0'th element in memory?
#define NULL ((void *)0)
The target device is an MSP430 and I am using IAR C. No operating system is used therefore no memory management is implemented
EDIT: Please note that I do not want to create a dummy variable. Also if there was a way to fool the compiler into optimizing the "d" parameter out without altering the function definition, this is preferable.
EDIT#2: I would rather not use the & operator in the function call as it generates inefficient code that I do not want to generate
EDIT#3: For those who don't believe me when I am talking about the & operator... the compiler manual states "Avoid taking the address of local variables using the & operator. This is inefficient
for two main reasons. First, the variable must be placed in memory, and thus cannot be placed in a processor register. This results in larger and slower code. Second, the optimizer can no longer assume that the local variable is unaffected over function calls."
No, it is not correct.
The C standard does not define the behavior when you do this. On many systems, it will cause an error (some sort of memory fault) when foo attempts to store to address 0. If it does not, then you will have written data to address 0, presumably overwriting something else there that may have been needed, so your system may fail at a later time.
You should change your function a bit to allow passing NULL
void foo(int a, int b, int* c, int* d)
{
if(c != NULL)
{
*c = a+b;
if(d != NULL)
{
*d = a+b+*c;
}
}
}
Now you can safely pass NULL. Otherwise, as the other answers already state, you end up dereferencing a NULL pointer which results in undefined behavior.
In your example, if you don't care about the pointer d and you pass NULL as you defined then it'll probably crash due to dereferencing NULL.
You should pass a valid pointer even if you don't care about the result.
Why not just declare a temporary and pass?
int tempd;
foo(a,b,&c, &tempd);
There is no such thing as the 0th element in memory due to virtual memory. However, if you attempt this, your program will crash with a memory exception. I assume you want to ignore d if it's null so simply do this:
if(d != NULL)
{
*d = a+b+*c
}
Since you don't want to create a dummy variable and can't change the function you'll most likely end up scribbling at the memory position 0 on your device whatever that means. Maybe it's a memory mapped hardware register, maybe it's just normal physical memory.
If it's a register, maybe it doesn't have any effect unless you write the magical value 4711 into it which will happen once every three months and the device halts and catches fire. (has happened to me, it's fun to overwrite the boot eeprom on a device)
Or if it's memory maybe you'll send a NULL pointer to a different function later and that function will happily read the value that this function wrote there and you'll end up at 5 in the morning tearing your hair out and yelling "this can't possibly affect that!". (has happened to me on some ancient unix that used to map the NULL page)
Maybe your compiler adds a safety net for you. Maybe it doesn't. Maybe the next version will. Maybe the next hardware revision will come with memory unmapped at address 0 and the device will halt.
I'd create a dummy variable in the calling function and move on to a more interesting problem, but if you're a stress junkie, pass NULL and see what happens (today or in 10 years).
In that specific example code both passed in pointers are dereferenced. Dereferencing NULL is undefined behavior. Just go with the dummy variables.
In general: If a function accepts a pointer it should state if the null pointer is a valid value for the argument. If it doesn't say anything stay on the safe side and just assume it isn't. This will safe you a lot of grief.
Interesting question! Generally speaking, NULL is reserved as an "invalid" address. You shouldn't try to write to it, but I don't think the standard specifies what should happen if you do. On a Windows machine, this will generate an access violation exception. I don't know what will happen on your device.
In any case, passing NULL is the usual way to indicate that you're not interested in the value of an out parameter. But your function must be aware of this and act accordingly:
if( c ) {
*c = a+b;
if( d ) {
*d = a+b+*c;
}
}
Edit
If you can't change the function definition, then you're probably out of luck. You can trick the compiler into not passing d if the calling convention is cdecl. Just declare the function without the d parameter:
extern void foo( int a, int b, int * c );
However, you're definitely into dangerous shenanigans territory here. The function definition will still expect the d paramater, so it will see random garbage.
The only other thing I can think of is passing a fixed address. Since you're writing for a specific device, there might be an address range that's safe to write to. (That is, it won't cause exceptions or corrupt actual memory.)
void * SafeAddress = (void *)0x12345678;
foo( a, b, &c, SafeAddress );
Of course, the easiest thing is to just use the dummy variable. I know you've said more than once that this generates inefficient code, but does that have to be the case? Does it make a difference if the dummy is a local variable versus a global one?
The function tries to store value at the provided address. If the address is invalid, then there will be a malfunction of some sort -- whether you use an operating system or not is irrelevant.
Either you have to give the function some valid address (even if you don't care for the value in a particular case), or you have to change the function so that it does not store the value (and, probably, does not even even compute it), if the address for it is NULL (which may or may not be 0x0 on your platform, BTW).
You keep repeating, that you "don't want" to do the former and can not do the latter. Well, then you have an unsolvable dilemma. Maybe, there already exists some other address, where dummy values like this can be stored in your program (or on the platform) -- you can pass that.
If there is no OS involved, then you must be dealing with some funky programmable device, which means, there ought to be seasoned C-programmers around you. Ask them for confirmation of what you are told here -- clearly, you aren't trusting the answers given to you by several people already.
It will try to assign something at memory location 0x0, so I'd say it will crash
I was able to save code space without increasing memory usage on the stack by declaring a dummy variable as well as a pointer to the dummy variable.
int Dummy;
int* Dummy_ptr = &Dummy;
This allowed the compiler to make optimizations on the function call as the & operator was not used in the function call.
The call is now
foo(a, b, c_ptr, Dummy_ptr);
EDIT: For those of you who don't believe me.
I took a look at the assembler. The Dummy variable exists on the stack, though because it is not used later on, and because it is only a return from the function the address is never passed to the function and any use of that variable in the function is optimized out.

How does temporary storage work in C when a function returns?

I know C pretty well, however I'm confused of how temporary storage works.
Like when a function returns, all the allocation happened inside that function is freed (from the stack or however the implementation decides to do this).
For example:
void f() {
int a = 5;
} // a's value doesn't exist anymore
However we can use the return keyword to transfer some data to the outside world:
int f() {
int a = 5;
return a;
} // a's value exists because it's transfered to the outside world
Please stop me if any of this is wrong.
Now here's the weird thing, when you do this with arrays, it doesn't work.
int []f() {
int a[1] = {5};
return a;
} // a's value doesn't exist. WHY?
I know arrays are only accessible by pointers, and you can't pass arrays around like another data structure without using pointers. Is this the reason you can't return arrays and use them in the outside world? Because they're only accessible by pointers?
I know I could be using dynamic allocation to keep the data to the outside world, but my question is about temporary allocation.
Thanks!
When you return something, its value is copied. a does not exist outside the function in your second example; it's value does. (It exists as an rvalue.)
In your last example, you implicitly convert the array a to an int*, and that copy is returned. a's lifetime ends, and you're pointing at garbage.
No variable lives outside its scope, ever.
In the first example the data is copied and returned to the calling function, however the second returns a pointer so the pointer is copied and returned, however the data that is pointed to is cleaned up.
In implementations of C I use (primarily for embedded 8/16-bit microcontrollers), space is allocated for the return value in the stack when the function is called.
Before calling the function, assume the stack is this (the lines could represent various lengths, but are all fixed):
[whatever]
...
When the routine is called (e.g. sometype myFunc(arg1,arg2)), C throws the parameters for the function (arguments and space for the return value, which are all of fixed length) on to the stack, followed by the return address to continue code execution from, and possibly backs up some processor registers.
[myFunc local variables...]
[return address after myFunc is done]
[myFunc argument 1]
[myFunc argument 2]
[myFunc return value]
[whatever]
...
By the time the function fully completes and returns to the code it was called from, all of it's variables have been deallocated off the stack (they might still be there in theory, but there is no guarantee)
In any case, in order to return the array, you would need to allocate space for it somewhere else, then return the address to the 0th element.
Some compilers will store return values in temporary registers of the processor rather than using the stack, but it's rare (only seen it on some AVR compilers).
When you attempt to return a locally allocated array like that, the calling function gets a pointer to where the array used to live on the stack. This can make for some spectacularly gruesome crashes, when later on, something else writes to the array, and clobbers a stack frame .. which may not manifest itself until much later, if the corrupted frame is deep in the calling sequence. The maddening this with debugging this type of error is that real error (returning a local array) can make some other, absolutely perfect function blow up.
You still return a memory address, you can try to check its value, but the contents its pointing are not valid beyond the scope of function,so dont confuse value with reference.
int []f() {
int a[1] = {5};
return a;
} // a's value doesn't exist. WHY?
First, the compiler wouldn't know what size of array to return. I just got syntax errors when I used your code, but with a typedef I was able to get an error that said that functions can't return arrays, which I knew.
typedef int ia[1];
ia h(void) {
ia a = 5;
return a;
}
Secondly, you can't do that anyway. You also can't do
int a[1] = {4};
int b[1];
b = a; // Here both a and b are interpreted as pointer literals or pointer arithmatic
While you don't write it out like that, and the compiler really wouldn't even have to generate any code for it this operation would have to happen semantically for this to be possible so that a new variable name could be used to refer the value that was returned by the function. If you enclosed it in a struct then the compiler would be just fine with copying the data.
Also, outside of the declaration and sizeof statements (and possibly typeof operations if the compiler has that extension) whenever an array name appears in code it is thought of by the compiler as either a pointer literal or as a chunk of pointer arithmetic that results in a pointer. This means that the return statement would end looking like you were returning the wrong type -- a pointer rather than an array.
If you want to know why this can't be done -- it just can't. A compiler could implicitly think about the array as though it were in a struct and make it happen, but that's just not how the C standard says it is to be done.

In C, do braces act as a stack frame?

If I create a variable within a new set of curly braces, is that variable popped off the stack on the closing brace, or does it hang out until the end of the function? For example:
void foo() {
int c[100];
{
int d[200];
}
//code that takes a while
return;
}
Will d be taking up memory during the code that takes a while section?
No, braces do not act as a stack frame. In C, braces only denote a naming scope, but nothing gets destroyed nor is anything popped off the stack when control passes out of it.
As a programmer writing code, you can often think of it as if it is a stack frame. The identifiers declared within the braces are only accessible within the braces, so from a programmer's point of view, it is like they are pushed onto the stack as they are declared and then popped when the scope is exited. However, compilers don't have to generate code that pushes/pops anything on entry/exit (and generally, they don't).
Also note that local variables may not use any stack space at all: they could be held in CPU registers or in some other auxiliary storage location, or be optimized away entirely.
So, the d array, in theory, could consume memory for the entire function. However, the compiler may optimize it away, or share its memory with other local variables whose usage lifetimes do not overlap.
The time during which the variable is actually taking up memory is obviously compiler-dependent (and many compilers don't adjust the stack pointer when inner blocks are entered and exited within functions).
However, a closely related but possibly more interesting question is whether the program is allowed to access that inner object outside the inner scope (but within the containing function), ie:
void foo() {
int c[100];
int *p;
{
int d[200];
p = d;
}
/* Can I access p[0] here? */
return;
}
(In other words: is the compiler allowed to deallocate d, even if in practice most don't?).
The answer is that the compiler is allowed to deallocate d, and accessing p[0] where the comment indicates is undefined behaviour (the program is not allowed to access the inner object outside of the inner scope). The relevant part of the C standard is 6.2.4p5:
For such an object [one that has
automatic storage duration] that does
not have a variable length array type,
its lifetime extends from entry into the block with which it is associated
until execution of that block ends in
any way. (Entering an enclosed block
or calling a function suspends, but
does not end, execution of the current
block.) If the block is entered
recursively, a new instance of the
object is created each time. The
initial value of the object is
indeterminate. If an initialization is
specified for the object, it is
performed each time the declaration is
reached in the execution of the block;
otherwise, the value becomes
indeterminate each time the
declaration is reached.
Your question is not clear enough to be answered unambiguously.
On the one hand, compilers don't normally do any local memory allocation-deallocation for nested block scopes. The local memory is normally allocated only once at function entry and released at function exit.
On the other hand, when the lifetime of a local object ends, the memory occupied by that object can be reused for another local object later. For example, in this code
void foo()
{
{
int d[100];
}
{
double e[20];
}
}
both arrays will usually occupy the same memory area, meaning that the total amount of the local storage needed by function foo is whatever is necessary for the largest of two arrays, not for both of them at the same time.
Whether the latter qualifies as d continuing to occupy memory till the end of function in the context of your question is for you to decide.
It's implementation dependent. I wrote a short program to test what gcc 4.3.4 does, and it allocates all of the stack space at once at the start of the function. You can examine the assembly that gcc produces using the -S flag.
No, d[] will not be on the stack for the remainder of routine. But alloca() is different.
Edit: Kristopher Johnson (and simon and Daniel) are right, and my initial response was wrong. With gcc 4.3.4.on CYGWIN, the code:
void foo(int[]);
void bar(void);
void foobar(int);
void foobar(int flag) {
if (flag) {
int big[100000000];
foo(big);
}
bar();
}
gives:
_foobar:
pushl %ebp
movl %esp, %ebp
movl $400000008, %eax
call __alloca
cmpl $0, 8(%ebp)
je L2
leal -400000000(%ebp), %eax
movl %eax, (%esp)
call _foo
L2:
call _bar
leave
ret
Live and learn! And a quick test seems to show that AndreyT is also correct about multiple allocations.
Added much later: The above test shows the gcc documentation is not quite right. For years it has said (emphasis added):
"The space for a variable-length array is deallocated as soon as the array name's scope ends."
They might. They might not. The answer I think you really need is: Don't ever assume anything. Modern compilers do all kinds of architecture and implementation-specific magic. Write your code simply and legibly to humans and let the compiler do the good stuff. If you try to code around the compiler you're asking for trouble - and the trouble you usually get in these situations is usually horribly subtle and difficult to diagnose.
Your variable d is typically not popped off the stack. Curly braces do not denote a stack frame. Otherwise, you would not be able to do something like this:
char var = getch();
{
char next_var = var + 1;
use_variable(next_char);
}
If curly braces caused a true stack push/pop (like a function call would), then the above code would not compile because the code inside the braces would not be able to access the variable var that lives outside the braces (just like a sub-function cannot directly access variables in the calling function). We know that this is not the case.
Curly braces are simply used for scoping. The compiler will treat any access to the "inner" variable from outside the enclosing braces as invalid, and it may re-use that memory for something else (this is implementation-dependent). However, it may not be popped off of the stack until the enclosing function returns.
Update: Here's what the C spec has to say. Regarding objects with automatic storage duration (section 6.4.2):
For an object that does not have a variable length array type, its
lifetime extends from entry into the block with which it is associated
until execution of that block ends in anyway.
The same section defines the term "lifetime" as (emphasis mine):
The lifetime of an object is the portion of program execution during
which storage is guaranteed to be reserved for it. An object exists,
has a constant address, and retains its last-stored value throughout
its lifetime. If an object is referred to outside of its lifetime, the
behavior is undefined.
The key word here is, of course, 'guaranteed'. Once you leave the scope of the inner set of braces, the array's lifetime is over. Storage may or may not still be allocated for it (your compiler might re-use the space for something else), but any attempts to access the array invoke undefined behavior and bring about unpredictable results.
The C spec has no notion of stack frames. It speaks only to how the resulting program will behave, and leaves the implementation details to the compiler (after all, the implementation would look quite different on a stackless CPU than it would on a CPU with a hardware stack). There is nothing in the C spec that mandates where a stack frame will or will not end. The only real way to know is to compile the code on your particular compiler/platform and examine the resulting assembly. Your compiler's current set of optimization options will likely play a role in this as well.
If you want to ensure that the array d is no longer eating up memory while your code is running, you can either convert the code in curly braces into a separate function or explicitly malloc and free the memory instead of using automatic storage.
I believe that it does go out of scope, but is not pop-ed off the stack until the function returns. So it will still be taking up memory on the stack until the function is completed, but not accessible downstream of the first closing curly brace.
There has already been given much information on the standard indicating that it is indeed implementation specific.
So, one experiment might be of interest. If we try the following code:
#include <stdio.h>
int main() {
int* x;
int* y;
{
int a;
x = &a;
printf("%p\n", (void*) x);
}
{
int b;
y = &b;
printf("%p\n", (void*) y);
}
}
Using gcc we obtain here two times the same address: Coliro
But if we try the following code:
#include <stdio.h>
int main() {
int* x;
int* y;
{
int a;
x = &a;
}
{
int b;
y = &b;
}
printf("%p\n", (void*) x);
printf("%p\n", (void*) y);
}
Using gcc we obtain here two different addresses: Coliro
So, you can't be really sure what is going on.

Resources