在什么时刻是本地变量分配存储?

时间:2010-03-01 05:07:00

标签: c++ memory-management

假设我们有以下内容:

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

int main ()
{
     print();
}

当前在函数print中调用的变量a的存储是在main中调用还是在执行到达函数内部的声明时?

5 个答案:

答案 0 :(得分:9)

这在很大程度上取决于编译器,但逻辑上,一旦声明变量就会分配存储。

考虑这个简单的C ++示例:

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

    return a + x;
}

当GCC编译时,会生成以下代码(;评论我的):

.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

_Z6addtwoi和.LCFI2之间的所有内容都是用于设置堆栈帧的样板代码(将先前函数的变量等安全地存储起来)。最后一个“subl $ 16,%esp”是局部变量x的分配。

.LCFI2是您键入的实际执行代码的第一位。 “movl $ 2,-4(%ebp)”将值2放入变量中。 (换句话说,初始化。)现在,您的空间已分配并初始化。之后,它将值加载到寄存器EDX中,然后将“8(%ebp)”中的参数移动到另一个寄存器EAX中。然后将两者加在一起,将结果保留在EAX中。现在,这是您实际输入的任何代码的结尾。剩下的只是样板。由于GCC要求在EAX中返回整数,因此不必对返回值进行任何操作。 “离开”指令拆除堆栈帧,“ret”指令将控制权返回给调用者。

TL; DR摘要:您可以将您的空间视为已使用块中的第一行可执行代码(配对{})进行分配。


我以为我会用解释性评论稍微清理一下,因为这是选定的答案。

答案 1 :(得分:3)

关于对象的构建:

构造发生在声明点,并且当对象超出范围时调用析构函数。

但是对象的构造和分配内存时不需要重合。

就像破坏对象和释放内存时一样,不需要重合。

关于实际分配堆栈内存的时间:

我不知道,但您可以通过以下代码查看:

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

  int x[1000000];
}

通过运行此代码,您可以看到异常发生的位置,对于我来说,在Visual Studio 2008上它会在函数输入时发生。它永远不会到达断点。

答案 2 :(得分:3)

作为Brian R. Bondy的回答的补充:很容易运行一些实验来展示它是如何工作的,比抛出堆栈空间错误更详细。请考虑以下代码:

#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;
}

这会在我的机器上产生此输出:

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

由于堆栈向下增长,我们可以看到事物被放入堆栈的顺序:a,b,d和c按顺序排列,然后两次调用foo()将它们放在同一个堆栈中放置两次。这意味着,即使多个变量声明(包括内部作用域内的一个)干预,也会在调用foo()时在堆栈上分配相同数量的内存。因此,在这种情况下,我们可以得出结论,main()中局部变量的所有堆栈内存都是在main()的开头分配的,而不是递增递增。

你还可以看到编译器排列的东西,以便按递减的堆栈顺序调用构造函数,并按升序调用析构函数 - 当构造它时,所有内容都是堆栈底部的构造的事物当它被破坏时,但意味着它是分配空间的底层事物,或者堆栈上面没有当前未使用的空间用于没有空间的东西已经建成(例如当c或foo的两个化身构成时d的空间)。

答案 3 :(得分:2)

这将取决于编译器,但通常在调用函数时会在堆栈上分配变量int a

答案 4 :(得分:2)

至少在通常实施的情况下,它介于两者之间。当您调用函数时,编译器将为函数调用生成代码,用于评估参数(如果有)并将它们放入寄存器或堆栈中。然后,当执行到达函数的条目时,将在堆栈上分配局部变量的空间,并初始化需要初始化的本地。此时,可能存在一些代码来保存函数使用的寄存器,随机移动值以使它们进入所需的寄存器等。在此之后,函数体的代码开始执行。