开发者

At what exact moment is a local variable allocated storage?

开发者 https://www.devze.com 2022-12-22 00:03 出处:网络
Suppose we have the following: void print() { int a;// declaration a = 9; cout << a << endl; } int main ()

Suppose we have the following:

void print()
{
     int a;  // declaration
     a = 9;
     cout << a << endl;
}

int main ()
{
     print();
}

Is the storage for variable a allocated at the moment function print is called in main or is it when execut开发者_StackOverflow社区ion reaches the declaration inside the function?


This is very much compiler dependent under the covers, but logically the storage is assigned as soon as the variable is declared.

Consider this simplistic C++ example:

// junk.c++
int addtwo(int a)
{
    int x = 2;

    return a + x;
}

When GCC compiles this, the following code is generated (; comments mine):

.file   "junk.c++"
    .text
.globl _Z6addtwoi
    .type   _Z6addtwoi, @function
_Z6addtwoi:
.LFB2:
    pushl   %ebp           ;store the old stack frame (caller's parameters and locals)
.LCFI0:
    movl    %esp, %ebp     ;set up the base pointer for our parameters and locals
.LCFI1:
    subl    $16, %esp      ;leave room for local variables on the stack
.LCFI2:
    movl    $2, -4(%ebp)   ;store the 2 in "x" (-4 offset from the base pointer)
    movl    -4(%ebp), %edx ;put "x" into the DX register
    movl    8(%ebp), %eax  ;put "a" (+8 offset from base pointer) into AX register
    addl    %edx, %eax     ;add the two together, storing the results in AX
    leave                  ;tear down the stack frame, no more locals or parameters
    ret                    ;exit the function, result is returned in AX by convention
.LFE2:
    .size   _Z6addtwoi, .-_Z6addtwoi
    .ident  "GCC: (Ubuntu 4.3.3-5ubuntu4) 4.3.3"
    .section    .note.GNU-stack,"",@progbits

Everything between _Z6addtwoi and .LCFI2 is boilerplate code used to set up the stack frame (store the previous function's variables, etc. safely out of the way). That last "subl $16, %esp" is the allocation of the local variable x.

.LCFI2 is the first bit of actual executing code that you've typed. "movl $2, -4(%ebp)" is putting the value 2 into the variable. (Initialization, in other words.) Now your space is allocated AND initialized. After that it loads the value into register EDX and follows that by moving your parameter, found in "8(%ebp)", into another register EAX. It then adds the two together, leaving the result in EAX. This is now the end of any code you've actually typed. The rest is again just boilerplate. Since GCC mandates that integers are returned in EAX, no work has to be done for the return value. The "leave" instruction tears down the stack frame and the "ret" instruction returns control back to the caller.

TL;DR summary: you can think of your space as having been allocated with the very first line of executable code in your block (paired {}).


I thought I'd clean this up a bit with explanatory comments seeing as this is the selected answer.


As for construction of objects:

Construction happen at the point of declaration, and the destructor is called when the object goes out of scope.

But construction of objects and when memory is allocated do not need to coincide.

Just as destruction of objects and when memory is deallocated do not need to coincide.

As for when the memory on the stack is actually allocated:

I don't know, but you could check via the following code:

void f()
{
  int y;
  y = 0;//breakpoint here

  int x[1000000];
}

By running this code you can see where the exception happens, for me on Visual Studio 2008 it happens on entry of the function. It never reaches the breakpoint.


As a supplement to Brian R. Bondy's answer: It's easy enough to run some experiments to show how this works, in a bit more detail than throwing out-of-stack-space errors. Consider this code:

#include<iostream>

void foo()
{
  int e; std::cout << "foo:e " << &e << std::endl;
}

int main()
{
  int a; std::cout << "a: " << &a << std::endl;
  foo();
  int b; std::cout << "b: " << &b << std::endl;
  {
    int c; std::cout << "c: " << &c << std::endl;
    foo();
  }
  int d; std::cout << "d: " << &d << std::endl;
}

This produces this output on my machine:

$ ./stack.exe
a: 0x28cd30
foo:e 0x28cd04
b: 0x28cd2c
c: 0x28cd24
foo:e 0x28cd04
d: 0x28cd28

Since the stack grows downward, we can see the order in which things are put onto the stack: a, b, d, and c in that order, and then the two calls to foo() put its e in the same place both times. This means that the same amount of memory has been allocated on the stack both times that foo() is called, even though several variable declarations (including one within an internal scope) intervene. Thus, in this case we can conclude that all the stack memory for the local variables in main() was allocated at the beginning of main() rather than being incremented incrementally.

You can also see that the compiler arranges things so that constructors are called in descending stack order, and destructors are called in ascending order -- everything is the bottom constructed thing on the stack when it's constructed and when it's destructed, but this does not mean that it's the bottom thing for which space has been allocated, or that there isn't currently-unused space above it on the stack for things that haven't been constructed yet (such as the space for d when c or the two incarnations of foo:e are constructed).


This will be compiler dependent, but typically the variable int a will be allocated on the stack at the time the function is called.


At least as things are typically implemented, it's in between the two. When you call a function, the compiler will generate code for the function call that evaluates the parameters (if any) and puts them in registers or on the stack. Then, when execution reaches the entry for the function, space for local variables will be allocated on the stack, and locals that need initialization will be initialized. At that point, there might be some code to save registers that are used by the function, shuffle values around to get them into the desired registers and such. The code for the body of the function starts to execute after that.

0

精彩评论

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

关注公众号