10

我担心我对 C 中的堆栈行为有误解。

假设我有以下代码:

int main (int argc, const char * argv[]) 
{
    int a = 20, b = 25;
    {
        int temp1;
        printf("&temp1 is %ld\n" , &temp1);
    }

    {
        int temp2;
        printf("&temp2 is %ld\n" , &temp2);
    }
    return 0;
}

为什么我在两个打印输出中没有得到相同的地址?我得到 temp2 与 temp1 相差一个 int,就好像 temp1 从未被回收一样。

我的期望是堆栈包含 20 和 25。然后将 temp1 放在顶部,然后将其移除,然后将 temp2 放在顶部,然后将其移除。

我在 Mac OS X 上使用 gcc。

请注意,我使用 -O0 标志进行编译而不进行优化。

那些想知道这个问题的背景的人:我正在准备关于 C 的教材,我试图向学生展示他们不仅应该避免从函数返回指向自动变量的指针,还应该避免从函数中获取变量的地址嵌套块并在外部取消引用它们。我试图演示这如何导致问题,但无法获取屏幕截图。

4

6 回答 6

19

编译器完全有权优化temp1temp2进入同一位置。自从编译器一次为一个堆栈操作生成代码以来,已经有很多年了。这些天来,整个堆栈框架都是一次性布局的。(几年前,一位同事和我想出了一个特别聪明的方法来做到这一点。)天真的堆栈布局可能会将每个变量放在自己的插槽中,即使在您的示例中它们的生命周期不重叠。

如果你很好奇,你可能会得到不同的结果gcc -O1gcc -O2

于 2009-03-21T23:23:57.630 回答
4

无论声明的顺序如何,都无法保证将收到什么地址堆栈对象。

编译器可以愉快地重新排序堆栈变量的创建和持续时间,只要它不影响函数的结果。

于 2009-03-21T23:23:21.733 回答
4

我相信 C 标准只是谈论在块中定义的变量的范围和生命周期。它没有承诺变量如何与堆栈交互,或者堆栈是否存在。

于 2009-03-21T23:27:11.880 回答
2

我记得读过一些关于它的东西。我现在只有这个不起眼的链接

只是为了让每个人都知道(并且为了存档),我们的内核扩展似乎遇到了 GCC 的已知限制。回顾一下,我们在一个非常可移植、非常轻量级的库中有一个函数,由于某种原因,当在 Darwin 上/为 Darwin 编译时,它会使用 1600+ 字节的堆栈进行编译。无论我尝试了哪些编译器选项,以及我使用了哪些优化级别,在可重现(但不频繁)的情况下,堆栈都不会小于 1400 次“机器检查”恐慌。

在网上进行了大量搜索,学习了一些 i386 汇编并与一些更擅长汇编的人交谈后,我了解到GCC 因具有可怕的堆栈分配而臭名昭著。[...]

显然这是 gcc 肮脏的小秘密,但对某些人来说并不是什么秘密——Linus Torvalds 在各种列表中多次抱怨 gcc 堆栈分配(在 lkml.org 中搜索“gcc 堆栈使用情况”)。一旦我知道要搜索什么,就会有很多抱怨gcc 对堆栈变量的低于标准分配,特别是它无法为不同范围内的变量重用堆栈空间。

话虽如此,我的 Linux 版本gcc正确地重用了堆栈空间,我得到了两个变量的相同地址。不确定 C 标准对此有何规定,但严格的范围强制仅对 C++ 中的代码正确性很重要(由于范围末尾的破坏),而在 C 中则不重要。

于 2009-03-21T23:28:41.127 回答
1

没有设置变量如何放置在堆栈上的标准。编译器中发生的事情要复杂得多。在您的代码中,编译器甚至可能选择完全忽略和抑制变量ab.

在编译器的许多阶段,代码可能会被转换为它的SSA 形式,并且所有堆栈变量都以这种形式失去了它们的地址和含义(它甚至可能使调试器更难)。

堆栈空间非常便宜,因为分配 2 个或 20 个变量的时间是恒定的。此外,对于大多数函数调用来说,堆栈空间是非常动态的,因为除了少数函数(那些更接近main()和线程入口函数,具有长寿命的事件循环等)之外,它们往往会很快完成。所以,你只是不要打扰他们。

于 2009-03-22T01:48:53.037 回答
0

这完全取决于编译器及其配置方式。

于 2009-03-21T23:20:16.390 回答