开发者

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.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜