了解堆栈分配的对象的释放

时间:2018-11-19 07:40:49

标签: c arrays memory-management

我试图了解堆栈分配对象的释放行为。确切地说,我正在尝试在标准(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已分配在堆栈上,而是由实现定义的。

3 个答案:

答案 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()已知的特殊内存位置,它允许您以任何顺序获取额外的内存,并且也可以以任何顺序返回---)