假设我们有以下内容:
void print()
{
int a; // declaration
a = 9;
cout << a << endl;
}
int main ()
{
print();
}
当前在函数print中调用的变量a的存储是在main中调用还是在执行到达函数内部的声明时?
答案 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)
至少在通常实施的情况下,它介于两者之间。当您调用函数时,编译器将为函数调用生成代码,用于评估参数(如果有)并将它们放入寄存器或堆栈中。然后,当执行到达函数的条目时,将在堆栈上分配局部变量的空间,并初始化需要初始化的本地。此时,可能存在一些代码来保存函数使用的寄存器,随机移动值以使它们进入所需的寄存器等。在此之后,函数体的代码开始执行。