繁体   English   中英

C ++堆栈和范围

[英]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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM