我试图了解堆栈分配对象的释放行为。确切地说,我正在尝试在标准(N1570)中找到解释。考虑以下简单功能:
void foo(){
char test[4096];
test[10] = 0;
}
在test
退出时,将释放foo
数组。在objdump
中很容易看出test
是在堆栈上分配的。标准(强调我的)指出:
一个其标识符声明为无链接且没有 存储类说明符static具有 自动存储期限 , 一些复合文字。
因此test
具有自动存储期限。我们可以轻松地重写函数,如下所示:
void test(){
char *test= malloc(4096 * sizeof(char));
test[10] = 0;
free(test);
}
但是我们必须自行分配它,但是test
仍然具有 自动存储期限 。
问题: :标准如何指定char test[4096]
将在函数退出时被释放?标准的 没有 声明test
已分配在堆栈上,而是由实现定义的。
答案 0 :(得分:6)
该标准描述了§6.2.4
的各种存储时间1对象的存储持续时间决定了其生存期。那里 有四个存储期限:静态,线程,自动和分配。 分配的存储在7.22.3中进行了描述。
2对象的生存期是程序执行期间的一部分 保证为其保留哪个存储。存在一个对象, 具有恒定的地址,并保留其最后存储的值 整个生命周期如果在对象之外引用对象 寿命,行为是不确定的。指针的值变为 不确定它所指向的对象(或刚刚过去的对象)何时到达 生命的尽头。
6对于这样一个没有可变长度数组类型的对象, 它的生命周期从进入它所在的块开始 关联,直到该块的执行以任何方式结束。 (输入 封闭的块或调用函数将挂起,但不会结束, 执行当前块。)如果以递归方式输入该块, 每次都会创建该对象的新实例。初始值 对象的不确定性。如果指定了初始化 对象,它在每次声明或复合时执行 在执行该块时到达文字;否则,价值 每次到达声明都会变得不确定。
因此,您非常正确。它根本没有描述存储本身何时以及如何释放。它仅指定何时可以使用定义良好的语义访问该存储。实现无需立即自动分配变量的存储时间,如果您希望程序符合标准,就不能碰它。
对于已分配的存储,情况也是如此,但要增加一点警告,您必须明确告知实现已完成存储。但是,即使您“免费”使用它,实现也可能会保留更长的时间。
在纸面上可能存在一个非常糟糕的实现,一个永远不会释放内存的实现。但是实际上,这些都是自然而然地被淘汰的,因为C的拙劣实现只是被大众所废弃,而被抛弃了。
答案 1 :(得分:1)
这与从分配器函数返回的已分配内存的生存期有关。对于内存分配器函数,明确声明(引自C11
,第7.22.3.1章,强调我的观点)
[...] 如果分配成功,则返回的指针将适当对齐,以便可以将其分配给 指向具有基本对齐要求的任何类型的对象的指针,然后使用 在分配的空间(直到空间)中访问此类对象或此类对象的数组 已明确释放)。分配对象的生命周期从分配开始 直到取消分配。 [...]
因此,您需要显式取消分配的内存。除非您明确地取消分配,否则分配的内存将保持 valid 的使用状态(如果处理不当,则会导致memory leak)。
OTOH,在第二个片段中,变量test
在函数出口处仍然超出范围,因为它具有自动存储功能。为变量test
分配的内存(strong:test
指向的内存)不再有效,并尝试访问未定义的行为。请记住,对于变量test
来说是正确的,即,在函数返回后,&test
变为无效,但是,由于test
指向的内存是通过分配器函数分配的,因此返回指针并从函数调用中使用它仍然是有效的访问。
再次引用规格
[...]如果在对象之外引用对象 寿命,行为是不确定的。 [...]
答案 2 :(得分:1)
第一个示例中的test
变量是4096个字节的数组。
第二个示例中的test
只是一个指针变量(4/8字节),您可以使用从malloc(3)
中获得的指针值来初始化堆空间。
两个test
变量(分别为4096和4/8字节)确实在程序离开例程时自动释放,,但是在第二个示例中您又分配了4096字节,从堆中,哪一种C语言都不知道(它们被分配在没有什么特殊要求的库例程中,因此,如果您没有明确地这样做,它们不会自动返回堆。)是malloc()已知的特殊内存位置,它允许您以任何顺序获取额外的内存,并且也可以以任何顺序返回---)