在输入和离开函数/块时堆栈上发生的事情,C99中的块和函数作用域之间有什么区别?
答案 0 :(得分:2)
当控件进入函数或块作用域时,执行所需要做的唯一事情就是表现就像直接在该作用域中使用“自动”创建了所有数据对象的新实例存储时间。“ 表现好像意味着它可以做一些不同的事情,只要正在编译的程序无法区分(或者只能通过做一些行为未定义的事情来区分)。例如,如果变量在函数范围内声明但仅在一个子块中使用,则编译器可以将其实时范围折叠到该子块,并且可能会,因为这会使寄存器分配更容易。
当控件退出函数或块作用域时,不需要实现任何。直接在该范围内的所有自动存储持续时间对象的生命周期结束,但没有程序可以在不触发未定义行为的情况下判断这是否发生。
不需要C实现具有堆栈,并且堆栈不是实现上述要求的唯一方式。例如,请参阅“Cheney on the M.T.A.”和c2:SpaghettiStack。
做具有堆栈的C实现通常会尝试避免在函数中间调整堆栈指针,原因太复杂,无法进入此处。这可能意味着具有块作用域的值在堆栈上比其声明的生存期更长,但仍然是未定义的行为来访问它。允许编译器为不再在范围内的值回收存储,但也允许为仍在范围内但不再被访问的值回收存储(在编译器中“死”)行话)。从历史上看,编译器对寄存器中的值比对堆栈插槽中的值执行更为积极,但同样,这种区别在您的实现上并不一定存在。答案 1 :(得分:2)
像这样:
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
当范围结束时,没有“发生”,但变量只存在于声明范围内(有一些神秘的例外)。由实现来重用已结束的先前嵌套作用域的变量空间。
答案 2 :(得分:2)
理论上,编译器可以生成代码,以便在进入包含局部变量的任何块时分配堆栈帧。在这种情况下,根本没有太大的区别。
实际上,大多数编译器计算可以通过函数的任何路径使用的局部变量的最大大小,然后在条目上分配堆栈帧的大小。函数内任何块中的变量都是与堆栈指针不同的偏移量。注意,在这种情况下,两个(或更多)块可以使用相同的地址。例如,使用如下源代码:
void f(int x) {
if (x) {
long y;
}
else {
float z;
}
}
...... y
和z
最终会出现在同一地址的机会非常好。