C99 中的块和函数作用域在进入和离开函数/块时在堆栈上发生的事情有什么区别?
3 回答
当控制进入函数或块作用域时,实现唯一需要做的事情就是表现得好像已经直接在该作用域中创建了具有“自动存储持续时间”的所有数据对象的新实例。表现得好像意味着它可以做一些不同的事情,只要正在编译的程序不能区分(或者只能通过做一些行为未定义的事情来区分)。例如,如果在函数范围内声明了一个变量,但只在一个子块中使用,编译器可以将其有效范围折叠到该子块,并且可能会这样做,因为这使得寄存器分配更容易。
当控制退出函数或块范围时,实现不需要做任何事情。直接在该范围内的所有自动存储持续时间对象的生命周期结束,但是没有程序可以在不触发未定义行为的情况下判断这已经发生。
C 实现不需要堆栈,堆栈不是实现上述要求的唯一方法。例如,参见“ MTA 上的切尼”和c2:SpaghettiStack。
确实有堆栈的C 实现通常会尽量避免在函数中间调整堆栈指针,原因太复杂,无法在此处讨论。这可能意味着具有块范围的值在堆栈上的存活时间比其声明的生命周期长,但访问它仍然是未定义的行为。允许编译器为不再在范围内的值回收存储,但也允许为仍在范围内但不再被访问的值(编译器术语中的“死”)回收存储。从历史上看,编译器对寄存器中的值比对堆栈槽中的值更积极,但同样,这在您的实现中不一定存在。
理论上,编译器可以生成代码以在任何包含局部变量的块的入口处分配堆栈帧。在这种情况下,根本不会有太大的区别。
在实践中,大多数编译器通过函数计算任何路径可以使用的局部变量的最大大小,然后在入口处分配该大小的堆栈帧。函数内任何块中的变量只是与堆栈指针不同的偏移量。请注意,在这种情况下,两个(或更多)块可能使用相同的地址。例如,使用这样的源代码:
void f(int x) {
if (x) {
long y;
}
else {
float z;
}
}
...机会非常好,y
最终z
会出现在同一个地址。
像这样:
void foo(int n) // <-- beginning of function scope
{ // <-- beginning of function body scope
int x = n;
for (;;)
{ // <-- beginning of block scope
int q = n;
x *= q;
} // <-- end of block scope
foo(x);
{ // <-- another block scope
int w = x;
}
} // <-- end of function body scope
// and of function scope
当范围结束时,什么都不会“发生”,但变量只存在于声明它的范围内(有一些神秘的例外)。重用已结束的先前嵌套范围的变量空间取决于实现。