[英]C++ stack and scope
我在Visual C ++ 2008上尝试了这个代码,它显示A和B没有相同的地址。
int main()
{
{
int A;
printf("%p\n", &A);
}
int B;
printf("%p\n", &B);
}
但是当B被定义时A不再存在,在我看来,相同的堆栈位置可以重用...
我不明白为什么编译器看起来不像一个非常简单的优化(例如在较大的变量和递归函数的上下文中可能很重要)。 并且似乎重用它不会在CPU和内存上更重。 有没有人对此有解释?
我想答案是“因为它比它看起来要复杂得多”,但老实说我不知道。
编辑 :关于以下答案和评论的一些准确性。
这段代码的问题在于,每次调用此函数时,堆栈都会“增加一个整数”。 当然,这在示例中没有问题,但考虑大变量和递归调用,并且您可以轻松避免堆栈溢出。
我建议的是内存优化,但我不知道它会如何损害性能。
顺便说一句,这种情况发生在发布版本中,将进行所有优化。
为这样的本地人重用堆栈空间是一种非常常见的优化。 事实上,在优化的构建中,如果你没有获取本地的地址,编译器甚至可能不会分配堆栈空间,变量只会存在于寄存器中。
由于多种原因,您可能看不到此优化。
首先,如果优化已关闭(如调试版本),编译器将不会执行其中任何一项以使调试更容易 - 您可以查看A的值,即使它不再在函数中使用。
如果您正在使用优化进行编译,我的猜测是,因为您正在获取本地的地址并将其传递给另一个函数,编译器不希望重用该存储,因为它不清楚该函数对该地址的作用。
还可以设想一个不使用此优化的编译器,除非函数使用的堆栈空间超过某个阈值。 我不知道有任何编译器这样做,因为重用不再使用的局部变量空间没有成本,可以全面应用。
如果堆栈增长是您的应用程序的一个严重问题,即在某些情况下您遇到堆栈溢出,您不应该依赖编译器的堆栈空间优化。 您应该考虑将堆栈上的大缓冲区移动到堆上,并努力消除非常深的递归。 例如,在Windows上,线程默认具有1 MB堆栈。 如果你担心溢出,因为你在每个堆栈帧上分配1k内存并进行1000次递归调用,修复不是试图诱使编译器从每个堆栈帧中节省一些空间。
为什么不看看大会?
我稍微改变了你的代码,以便int A = 1; 和int B = 2; 使其更容易破译。
从g ++默认设置:
.globl main
.type main, @function
main:
.LFB2:
leal 4(%esp), %ecx
.LCFI0:
andl $-16, %esp
pushl -4(%ecx)
.LCFI1:
pushl %ebp
.LCFI2:
movl %esp, %ebp
.LCFI3:
pushl %ecx
.LCFI4:
subl $36, %esp
.LCFI5:
movl $1, -8(%ebp)
leal -8(%ebp), %eax
movl %eax, 4(%esp)
movl $.LC0, (%esp)
call printf
movl $2, -12(%ebp)
leal -12(%ebp), %eax
movl %eax, 4(%esp)
movl $.LC0, (%esp)
call printf
movl $0, %eax
addl $36, %esp
popl %ecx
popl %ebp
leal -4(%ecx), %esp
ret
.LFE2:
最终它看起来像编译器只是没有把它们放在同一个地址。 没有花哨的前瞻优化。 要么它没有尝试优化,要么它决定没有任何好处。
注意A已分配,然后打印。 然后分配和打印B,就像在原始来源中一样。 当然,如果您使用不同的编译器设置,这可能看起来完全不同。
据我所知,B的空间是在进入main时保留的,而不是在线上
int B;
如果你在该行之前打破调试器,你仍然可以获得B的地址。在此行之后,stackpointer也不会改变。 在这一行发生的唯一事情就是调用B的构造函数。
可能是编译器将两者放在同一堆栈帧上。 因此,即使A在其范围之外无法访问,编译器也可以自由地将其绑定到内存中的某个位置,只要它不会破坏代码的语义。 简而言之,在执行main时,它们都会被放在堆栈中。
在 B.之后在代码中的A 之后声明了A(在C90不允许的情况下),在堆栈上分配了A,但是它仍然在主函数的顶部范围内,因此从主函数的开头直到结束。 因此在主要启动时按下B,在输入内部范围时按下A,在离开时按下弹出,然后在保留主要功能时弹出B.
我的工作很大一部分是打击编译器,我不得不说他们并不总是做我们人类期望他们做的事情。 即使编写了编译器,您仍然会对结果感到惊讶,输入矩阵无法100%预测。
编译器的优化部分非常复杂,正如其他答案中所提到的,您观察到的可能是由于对设置的自愿响应,但它可能只是周围代码的影响,甚至没有这种逻辑优化。
无论如何,正如Micheal所说,你不应该依赖编译器来防止堆栈溢出,因为你可能只是将问题推迟到以后,当使用正常的代码维护或一组不同的输入时,它会崩溃很多进一步在管道中,可能在用户手中。
在这种情况下,编译器别无选择。 它不能假设printf()
任何特定行为。 因此,只要A本身存在,就必须假设printf()
可以挂在&A
。 因此,A本身存在于定义它的整个范围内。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.