如果变量离开作用域,为什么 C 不递减堆栈指针?
Why does C not decrement the stack pointer if a variable leaves the scope?
#include <stdio.h>
void main() {
{
int x;
printf("%p\n", &x);
}
{
int x;
printf("%p\n", &x);
}
}
我认为 运行 它会输出相同的内容两次。当它声明第一个变量时,它递增堆栈指针,但随后离开作用域,因此它递减它,然后第二次重复该过程,因此 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);
}
}
这是为什么?我误会了什么?
C 标准甚至没有提到堆栈。编译器可以在不需要时自由优化变量。 C 标准中绝对没有任何内容暗示打印输出不应该相等或不相等。
在我的计算机上,这通过根据优化级别提供不同的输出来体现:
$ gcc c.c
/tmp$ ./a.out
0x7ffd8733c3ac
0x7ffd8733c3a8
/tmp$ gcc c.c -O3
/tmp$ ./a.out
0x7fff4e91544c
0x7fff4e91544c
In fact, the first "int x" is still reachable even though its scope is gone.
访问超出范围的变量会导致未定义的行为,这意味着任何事情都可能发生。这包括程序按预期运行的情况。
这是您的第二个代码段经过不同优化后的输出:
/tmp$ ./a.out
0x7ffd4df94864
0x7ffd4df94860
0x7ffd4df9485c
10 25 71
/tmp$ gcc c.c -O3
/tmp$ ./a.out
0x7ffc30b4e44c
0x7ffc30b4e44c
0x7ffc30b4e44c
0 0 71
当您根据优化级别获得不同的行为时,这几乎是 100% 的迹象表明您的程序存在导致未定义行为的问题。您在编译器中遇到错误的可能性非常非常小。除了这两个原因,我想不出其他原因。
作为实际问题,当堆栈确实存在时(正如 Broman 的回答所指出的,不需要堆栈存在,尽管 需要支持递归) 编译器通常会生成仅在函数进入时调整堆栈指针一次的代码,并在函数退出时再次调整一次,即使函数中有 sub-scopes 限制单个变量的生命周期。
如果您习惯于手工编写汇编语言,这可能看起来很奇怪。最基本的原因是,它意味着每个驻留在堆栈上的变量在整个函数中都有一个 "stack slot" 和一个 固定位置 ,这为编译器提供了最大的灵活性移动机器指令以进行优化。
#include <stdio.h>
void main() {
{
int x;
printf("%p\n", &x);
}
{
int x;
printf("%p\n", &x);
}
}
我认为 运行 它会输出相同的内容两次。当它声明第一个变量时,它递增堆栈指针,但随后离开作用域,因此它递减它,然后第二次重复该过程,因此 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);
}
}
这是为什么?我误会了什么?
C 标准甚至没有提到堆栈。编译器可以在不需要时自由优化变量。 C 标准中绝对没有任何内容暗示打印输出不应该相等或不相等。
在我的计算机上,这通过根据优化级别提供不同的输出来体现:
$ gcc c.c
/tmp$ ./a.out
0x7ffd8733c3ac
0x7ffd8733c3a8
/tmp$ gcc c.c -O3
/tmp$ ./a.out
0x7fff4e91544c
0x7fff4e91544c
In fact, the first "int x" is still reachable even though its scope is gone.
访问超出范围的变量会导致未定义的行为,这意味着任何事情都可能发生。这包括程序按预期运行的情况。
这是您的第二个代码段经过不同优化后的输出:
/tmp$ ./a.out
0x7ffd4df94864
0x7ffd4df94860
0x7ffd4df9485c
10 25 71
/tmp$ gcc c.c -O3
/tmp$ ./a.out
0x7ffc30b4e44c
0x7ffc30b4e44c
0x7ffc30b4e44c
0 0 71
当您根据优化级别获得不同的行为时,这几乎是 100% 的迹象表明您的程序存在导致未定义行为的问题。您在编译器中遇到错误的可能性非常非常小。除了这两个原因,我想不出其他原因。
作为实际问题,当堆栈确实存在时(正如 Broman 的回答所指出的,不需要堆栈存在,尽管 需要支持递归) 编译器通常会生成仅在函数进入时调整堆栈指针一次的代码,并在函数退出时再次调整一次,即使函数中有 sub-scopes 限制单个变量的生命周期。
如果您习惯于手工编写汇编语言,这可能看起来很奇怪。最基本的原因是,它意味着每个驻留在堆栈上的变量在整个函数中都有一个 "stack slot" 和一个 固定位置 ,这为编译器提供了最大的灵活性移动机器指令以进行优化。