如果变量离开作用域,为什么C不会递减堆栈指针?

Ame*_*man 5 c stack-pointer

#include <stdio.h>
void main() {
    {
        int x;
        printf("%p\n", &x);
    }
    {
        int x;
        printf("%p\n", &x);
    }
}
Run Code Online (Sandbox Code Playgroud)

我认为运行此命令将输出相同的内容两次。当它声明第一个变量时,它会递增堆栈指针,但会离开作用域,因此会递减它,然后第二次重复该过程,因此int x两次都将占用堆栈上的相同内存位置。

但是事实并非如此。堆栈指针不会递减,int x在两种情况下都将占用堆栈中的不同位置。实际上,int x即使范围已消失,第一个仍然可以访问。

#include <stdio.h>
void main() {
    {
        int x = 10;
        printf("%p\n", &x);
    }
    {
        int x = 25;
        printf("%p\n", &x);
    }
    {
        int x = 71;
        printf("%p\n", &x);

        int *p = &x;
        printf("%i %i %i\n", *(p + 2), *(p + 1), *p);
    }
}
Run Code Online (Sandbox Code Playgroud)

为什么是这样?我有什么误会?

klu*_*utt 7

C标准甚至没有提到堆栈。不需要变量时,编译器可以自由地优化变量。C标准中绝对没有任何内容暗示打印输出不应相等或不相等。

在我的计算机上,这通过根据优化级别提供不同的输出来体现出来:

$ gcc c.c

/tmp$ ./a.out 
0x7ffd8733c3ac
0x7ffd8733c3a8

/tmp$ gcc c.c -O3

/tmp$ ./a.out 
0x7fff4e91544c
0x7fff4e91544c
Run Code Online (Sandbox Code Playgroud)

实际上,即使第一个“ int x”的范围已消失,仍然可以访问。

访问超出范围的变量会导致未定义的行为,这意味着任何事情都可能发生。这包括程序按预期工作的情况。

这是您的第二个代码段采用不同优化的输出:

/tmp$ ./a.out 
0x7ffd4df94864
0x7ffd4df94860
0x7ffd4df9485c
10 25 71

/tmp$ gcc c.c -O3

/tmp$ ./a.out 
0x7ffc30b4e44c
0x7ffc30b4e44c
0x7ffc30b4e44c
0 0 71
Run Code Online (Sandbox Code Playgroud)

当您根据优化级别获得不同的行为时,几乎有100%的迹象表明您的程序中有某些东西会导致未定义的行为。您在编译器中遇到错误的机会非常小。除了这两个原因外,我想不出其他任何可能的原因。

  • 反过来,这意味着编译器可以完全自由地生成所需的任何代码,因此,是的,即使某些情况比其他情况更有可能,结果程序也可以是任何东西。最后,基本上还是一样。您破坏了程序的可预测性。 (4认同)

zwo*_*wol 5

作为一个实际问题,当一个堆栈确实存在(如Broman的回答指出,对于一个堆栈存在没有要求,尽管要支持递归要求)编译器通常会生成代码,调整堆栈指针只在函数一次进入,然后再次退出,即使函数中存在子范围限制单个变量的生存期。

如果您习惯于手工编写汇编语言,这可能看起来很奇怪。最基本的原因是,这意味着驻留在堆栈上的每个变量都有一个“堆栈插槽”,在整个函数中的位置固定,这为编译器在移动机器指令以进行优化方面提供了最大的灵活性。