Answers:
不,大括号不能用作堆栈框架。在C语言中,花括号仅表示命名范围,但是当控制权移出时,不会破坏任何内容,也不会弹出任何内容。
作为程序员编写代码,您通常可以将其视为堆栈框架。大括号内声明的标识符只能在大括号内访问,因此从程序员的角度来看,就像将标识符声明时将其压入堆栈,然后在退出作用域时将其弹出一样。但是,编译器不必生成在进入/退出时推送/弹出任何内容的代码(通常,它们不需要这样做)。
还要注意,局部变量可能根本不使用任何堆栈空间:它们可以保存在CPU寄存器中或其他辅助存储位置中,或者可以完全优化掉。
因此,从d
理论上讲,数组可能会消耗整个函数的内存。但是,编译器可能会对其进行优化,或者与使用期限不重叠的其他局部变量共享其内存。
变量实际占用内存的时间显然取决于编译器(并且在函数中进入和退出内部块时,许多编译器不会调整堆栈指针)。
但是,一个密切相关但可能更有趣的问题是,是否允许程序访问内部作用域之外(但在包含函数之内)的内部对象,即:
void foo() {
int c[100];
int *p;
{
int d[200];
p = d;
}
/* Can I access p[0] here? */
return;
}
(换句话说:即使实际上大多数情况下都不允许编译器进行dealloc d
)?
答案是编译器被允许解除分配d
,并访问p[0]
其中的注释表示是未定义的行为(程序没有允许访问的范围内的内对象外)。C标准的相关部分是6.2.4p5:
对于这种不具有可变长度数组类型的对象[具有自动存储持续时间的对象], 其生存期从进入与之关联的块的时间开始,直到该块的执行以任何方式结束。(进入一个封闭的块或调用一个函数会暂停,但不会结束当前块的执行。)如果递归地输入该块,则每次都会创建该对象的新实例。对象的初始值不确定。如果为对象指定了初始化,则每次在执行块时到达声明时都将执行;否则,每次到达声明时,该值将变得不确定。
不,d [] 在例程的其余部分中不会处于堆栈中。但是alloca()是不同的。
编辑:克里斯托弗·约翰逊(和克里斯汀·丹尼尔)是对的,而我最初的回答是错误的。在CYGWIN上使用gcc 4.3.4。时,代码:
void foo(int[]);
void bar(void);
void foobar(int);
void foobar(int flag) {
if (flag) {
int big[100000000];
foo(big);
}
bar();
}
给出:
_foobar:
pushl %ebp
movl %esp, %ebp
movl $400000008, %eax
call __alloca
cmpl $0, 8(%ebp)
je L2
leal -400000000(%ebp), %eax
movl %eax, (%esp)
call _foo
L2:
call _bar
leave
ret
活到老,学到老!快速测试似乎表明,AndreyT在多重分配方面也是正确的。
以后再添加:上述测试表明gcc文档不太正确。多年来,它一直在说(强调):
“ 一旦数组名称的作用域结束,就将释放可变长度数组的空间 。”
alloca
函数。cygwin gcc会做到这一点让我感到非常惊讶。它甚至不是可变长度的数组,所以IDK为什么要提起它。
您的变量d
通常不会从堆栈中弹出。花括号不表示堆栈框架。否则,您将无法执行以下操作:
char var = getch();
{
char next_var = var + 1;
use_variable(next_char);
}
如果大括号引起了真正的堆栈推入/弹出操作(如函数调用一样),则上述代码将无法编译,因为大括号内的代码将无法访问var
位于大括号外的变量(就像子函数一样)。函数不能直接访问调用函数中的变量)。我们知道事实并非如此。
花括号仅用于范围定义。编译器会将从括号括起来的外部对“内部”变量的任何访问都视为无效,并且它可能会将该内存重新用于其他用途(这取决于实现)。但是,直到封闭函数返回之前,它可能不会从堆栈中弹出。
更新: 这是C规范必须说的。关于具有自动存储期限的对象(第6.4.2节):
对于不具有可变长度数组类型的对象,其生存期从进入与之关联的块开始一直到该块的执行结束为止。
同一部分将“生存期”定义为(强调我的):
对象的生存期是程序执行的一部分,在此期间保证为其保留存储空间。一个对象存在,具有恒定的地址,并在其整个生命周期内保留其最后存储的值。如果在其生存期之外引用对象,则行为是不确定的。
当然,这里的关键词是“保证的”。离开内部括号的范围后,数组的生命周期就结束了。可能会或可能不会为它分配存储空间(您的编译器可能会重新使用该空间用于其他用途),但是任何访问数组的尝试都会导致未定义的行为并带来不可预测的结果。
C规范没有堆栈帧的概念。它仅说明结果程序的行为方式,并将实现细节留给编译器(毕竟,无堆栈CPU上的实现看起来与带硬件堆栈的CPU上的实现完全不同)。C规范中没有任何内容规定堆栈帧将在何处结束。唯一真正了解的方法是在特定的编译器/平台上编译代码并检查生成的程序集。您的编译器当前的优化选项集也可能在其中发挥作用。
如果你想确保阵列d
不再吃了,而你的代码运行内存,可以在大括号中的代码或者转换成一个单独的函数或明确malloc
和free
内存,而不是使用自动存储。
我相信它的确超出了范围,但是直到函数返回时才弹出堆栈。因此,在函数完成之前,它仍将占用堆栈上的内存,但在第一个闭合花括号的下游无法访问。
已经有很多关于该标准的信息,表明它确实是特定于实现的。
因此,可能需要进行一项实验。如果我们尝试以下代码:
#include <stdio.h>
int main() {
int* x;
int* y;
{
int a;
x = &a;
printf("%p\n", (void*) x);
}
{
int b;
y = &b;
printf("%p\n", (void*) y);
}
}
使用gcc,我们在这里获得两次相同的地址:Coliro
但是,如果我们尝试以下代码:
#include <stdio.h>
int main() {
int* x;
int* y;
{
int a;
x = &a;
}
{
int b;
y = &b;
}
printf("%p\n", (void*) x);
printf("%p\n", (void*) y);
}
使用gcc我们在这里获得两个不同的地址:Coliro
因此,您无法确定到底发生了什么。