在C中,大括号是作为一个堆栈框架吗?
如果我在一组新的花括号中创build一个variables,那么这个variables是从右花括号中的栈上popup的,还是挂起直到函数结束? 例如:
void foo() { int c[100]; { int d[200]; } //code that takes a while return; }
在code that takes a while
的code that takes a while
会不会占用内存?
不,大括号不作为一个堆栈框架。 在C中,大括号只是表示一个命名范围,但是没有任何东西被破坏,当控制通过它时,任何东西都不会从堆栈中popup。
作为编写代码的程序员,您经常可以将其看作是堆栈框架。 在大括号中声明的标识符只能在大括号内被访问,所以从程序员的angular度来看,就像它们被声明时被压入栈一样,然后在范围退出时被popup。 但是,编译器不必生成在入口/出口处推出/popup任何东西的代码(而且通常不会)。
还要注意,局部variables可能根本不使用任何堆栈空间:它们可以保存在CPU寄存器或其他辅助存储位置,也可以完全优化。
所以,理论上讲, d
数组可以消耗整个函数的内存。 但是,编译器可能会优化它,或与其使用寿命不重叠的其他本地variables共享内存。
variables实际占用内存的时间显然是依赖于编译器的(当内部块被input并退出函数时,许多编译器不会调整堆栈指针)。
然而,一个密切相关但可能更有意思的问题是程序是否被允许访问内部范围之外(但在包含函数内)的内部对象,即:
void foo() { int c[100]; int *p; { int d[200]; p = d; } /* Can I access p[0] here? */ return; }
(换句话说:编译器是否允许释放d
,即使在实践中大多数情况下不会)?
答案是肯定的,编译器被允许释放d
,并访问注释表明是未定义行为的p[0]
。 C标准的相关部分是6.2.4p5:
对于没有可变长度数组types的此类对象[具有自动存储持续时间的对象], 其生存期从入口延伸到与其关联的块,直到该块的执行以任何方式结束 。 (input一个封闭的块或者调用一个函数会挂起,但不会结束,执行当前块。)如果块是recursion地input的,每次都会创build一个新的对象实例。 对象的初始值是不确定的。 如果为对象指定了初始化,则在执行块时每次执行声明时执行初始化; 否则,每次达到声明时该值都变得不确定。
你的问题不够清楚,无法明确地回答。
一方面,编译器通常不会为嵌套块作用域执行任何本地内存分配 – 解除分配。 本地内存通常只在函数入口处分配一次,在函数出口处释放。
另一方面,当本地对象的生命周期结束时,该对象占用的内存可以在稍后被重新用于另一个本地对象。 例如,在这个代码中
void foo() { { int d[100]; } { double e[20]; } }
两个数组通常会占据相同的内存区域,这意味着函数foo
所需的本地存储总量是两个数组中最大的数组所需要的数量 ,而不是两个数组同时需要的数量。
后者是否符合在你的问题的范围内继续占用记忆直到function的结束是由你来决定的。
它依赖于实现。 我写了一个简短的程序来testinggcc 4.3.4的function,它在函数的开头一次分配所有的堆栈空间。 您可以使用-S标志检查gcc生成的程序集。
不,d []将不会在例程的其余部分处于堆栈中。 但是alloca()是不同的。
编辑:克里斯托弗约翰逊(和西蒙和丹尼尔)是正确的 ,我最初的反应是错误的 。 使用gcc 4.3.4.on CYGWIN,代码:
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
活到老,学到老! 而且一个快速testing似乎表明,AndreyT对于多个分配也是正确的。
稍后添加 :以上testing显示gcc文档不太正确。 多年来(已经强调):
“只要数组名称的作用域 结束 ,就释放一个可变长度数组的空间 。
他们可能。 他们可能不会。 我想你真的需要的答案是: 永远不要做任何事情。 现代编译器完成各种架构和特定实现的魔法。 把你的代码简单明了地写给人类,让编译器做好这些事情。 如果你试图在编译器周围编写代码,那么你会遇到麻烦,而且在这些情况下通常会遇到的麻烦通常非常微妙,很难诊断。
你的variablesd
通常不会popup堆栈。 大括号不表示一个堆栈框架。 否则,你将无法做到这样的事情:
char var = getch(); { char next_var = var + 1; use_variable(next_char); }
如果大括号引起了一个真正的堆栈push / pop(就像函数调用一样),那么上面的代码就不会编译,因为大括号内的代码将无法访问大括号外的variablesvar
(就像subfunction不能直接访问调用函数中的variables)。 我们知道情况并非如此。
大括号只用于范围界定。 编译器将把对内部variables的任何访问都视为无效,并且可能会将这个内存重用到别的东西(这是依赖于实现的)。 但是,在封闭函数返回之前,它可能不会popup堆栈。
更新:这是C规范要说的。 关于具有自动存储时间的对象(第6.4.2节):
对于没有可变长度数组types的对象,其生存期从入口延伸到与其关联的块,直到该块的执行结束。
同一部分将术语“生命”定义为(强调我的):
对象的生命周期是程序执行的一部分,在此期间保证存储器的存储空间。 存在一个对象,具有一个常量地址,并在其整个生命周期中保留其最后存储的值。 如果一个对象被引用到其生命周期之外,那么这个行为是不确定的。
这里的关键词当然是“保证”的。 一旦你离开了大括号内的范围,数组的寿命就结束了。 存储可能还是不会被分配给它(你的编译器可能会重新使用这个空间来做其他事情),但是任何访问数组的尝试都会调用未定义的行为并导致不可预知的结果。
C规范没有堆栈框架的概念。 它只谈到结果程序的行为,并将实现细节留给编译器(毕竟,在无堆栈CPU上的实现与在具有硬件堆栈的CPU上的实现看起来完全不同)。 C规范没有规定堆栈帧将会或不会结束的地方。 唯一真正的方法是编译你的特定编译器/平台上的代码,并检查生成的程序集。 编译器当前的一组优化选项也可能在其中扮演一个angular色。
如果要确保在代码运行时数组d
不再耗尽内存,则可以将花括号中的代码转换为单独的函数,或者显式地malloc
并free
内存,而不是使用自动存储。
我相信它会超出范围,但是在函数返回之前不会popup堆栈。 所以它仍然会占用堆栈中的内存,直到函数完成,但不能在第一个closures花括号的下游访问。