C中的奇怪堆栈行为

时间:2009-03-21 23:15:10

标签: c stack

我担心我误解了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的教材,我试图向学生们展示他们不仅应该避免从函数返回指向自动变量的指针,而且还要避免使用地址来自嵌套块的变量和在外部解除引用它们。我试图证明这是如何导致问题的,并且无法获得截图。

6 个答案:

答案 0 :(得分:19)

编译器完全在其 的权限范围内,无法将temp1temp2优化到同一位置。编译器一次为一个堆栈操作生成代码已经很多年了;这些天整个堆栈框架一次性布局。 (几年前,一位同事和我想出了一个particularly clever way to do this。)朴素的堆栈布局可能会将每个变量放在自己的插槽中,即使在你的例子中,它们的生命周期也不会重叠。

如果您感到好奇,gcc -O1gcc -O2可能会得到不同的结果。

答案 1 :(得分:4)

无论是否声明它们的顺序,都无法保证堆栈对象将接收哪些地址。

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

答案 2 :(得分:4)

我认为C标准只讨论了块中定义的变量的范围和生命周期。它没有声明变量如何与堆栈交互或者堆栈是否存在。

答案 3 :(得分:2)

我记得读过一些关于它的事情。我现在拥有的只是obscure link

  

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

     

经过大量的网络搜索,学习一些i386程序集并与一​​些在组装方面做得更好的人交谈后,我了解到 GCC因为有可怕的堆栈分配而臭名昭着。 [...]

     

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

话虽如此,我的Linux版gcc正确地重新使用了堆栈空间,我得到两个变量的相同地址。不确定C标准对它的说法,但严格的范围实施只对C ++中的代码正确性很重要(由于范围末尾的破坏),而不是C语言。

答案 4 :(得分:1)

没有标准设置变量放置在堆栈上的方式。编译器中发生的事情要复杂得多。在您的代码中,编译器甚至可以选择完全忽略和禁止变量ab

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

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

答案 5 :(得分:0)

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