【问题标题】:Why do I have to use free on a pointer but not a normal declaration?为什么我必须在指针上使用 free 而不是普通声明?
【发布时间】:2010-10-12 05:58:53
【问题描述】:

为什么我在声明指针时必须使用free()例如:

int *temp = (int*)malloc(sizeof(int))
*temp = 3;

但不是在我这样做的时候:

int temp = 3;

【问题讨论】:

    标签: c pointers free


    【解决方案1】:

    普通声明被放入堆栈。当函数返回时,堆栈指针恢复到函数调用前的值,因此内存被自动回收。

    基于 Malloc 的声明是从“堆”分配的,这需要程序员管理分配和解除分配。

    【讨论】:

    • 这是否也适用于全局声明的变量?
    • 不,AFAICR 全局声明的变量在“数据段”中分配空间 - 由操作系统根据应用程序的已知存储要求留出的单独内存块。
    • 全局变量在卸载包含它们的模块或进程终止时被“释放”。
    • Umm.. 如果涉及 malloc、calloc、realloc 或 new,那么最好涉及 free/delete。这也适用于全局声明的指针。基本上,如果内存是从堆中分配的,那么它必须返回到堆中。
    • 不想学究气,但“堆栈”和“堆”是实现细节。 K&R 没有提及它们,C99 标准也没有提及。
    【解决方案2】:

    您不必总是在指针上使用 free,只需使用 malloc 声明的指针即可。你可以声明一个指向栈上内存位置的指针

    int a = 3;
    int* p = &a;
    

    当它超出范围时,这个内存(连同指针)也将被自动释放。使用 malloc 会在堆上分配相同的内存,因此您必须手动处理清理。

    【讨论】:

    • 只是为了澄清:指针变量是在堆栈上创建的,它们保存在堆上分配的数据的内存地址。它们在 32 位系统上占用堆栈上的 4 个字节,在 64 位系统上占用堆栈上的 8 个字节。
    • @Brian:感谢您的澄清。我认为我的修改使我的回答在技术上更加准确。
    【解决方案3】:

    因为语言让您在堆栈和堆之间进行选择。

    为什么要在堆栈和堆之间进行选择的原因:

    • 堆上的变量故意不释放自己,以便您可以在代码块或函数范围之外使用它们。
    • 与堆相比,使用堆栈的效率更高
    • 在大多数编译器上,您无法在运行时选择堆栈上的对象或数组的大小,因此此处将使用堆。

    为什么不能自动释放堆:

    因为没有办法知道你何时完成了记忆。有一些方法可以模拟垃圾收集,但这涉及到堆栈上没有更多变量并且堆上持有指向堆上数据的指针的情况。

    更多关于堆栈与堆的信息:

    C 语言让您选择是否要在堆栈或堆上定义变量。

    • 堆栈上的变量在脱离scope 时会自动释放。
    • 堆上的变量不会自动释放。

    malloc 在堆上创建变量。一个简单的声明,例如 int x;在堆栈上创建一个变量。

    See further reading on stack vs heap in my answer here

    指针:

    澄清一下:指针变量是在堆栈上创建的,它们保存在堆上分配的数据的内存地址。它们在 32 位系统上占用堆栈上的 4 个字节,在 64 位系统上占用堆栈上的 8 个字节。

    【讨论】:

    • 只是出于兴趣,因为我从未看过 - 编译器是否必须在每次输入代码块时创建一个新的堆栈帧,并在所述块的末尾恢复堆栈指针?
    • @Alnitak:我猜这取决于优化级别 - 请记住,编译器甚至可能内联您的代码并完全摆脱函数调用!
    • 我更多的是考虑函数内部的块,而不是函数调用。
    • 我认为它总是使用 Alnitak,但可以优化出来。
    • @Alnitak:至少对于 gcc,afaik 堆栈帧仅在函数入口处创建(即push ebpmov ebp, espsub esp, SIZEOF_LOCAL_VARS)并且块创建不会移动堆栈指针;我可能是错的,所以把这个和一粒盐放在一起;)
    【解决方案4】:

    应该注意的是,C 没有堆栈或堆的概念,尽管前面的答案在大约 99% 的情况下是正确的,并且提供了很好的洞察力。

    C 为对象定义了三种存储持续时间:staticautomaticallocated。 (§6.2.4.1)

    静态对象(例如全局变量)在整个程序期间都可用。

    只要变量在范围内,自动对象就存在。一旦超出范围,它们就会不复存在。

    请注意,这是两个极端。 C 给你一个介于两者之间的点:Allocated 对象。 (搜索词将是动态分配的内存。)通过这些,告诉计算机对象应该何时开始和结束它们的存在。这是通过使用标准函数 malloc()(或衍生函数)和 free() 来完成的。

    严格来说,您不必必须调用free()。或者也许你会这样做(你必须阅读标准以获得权威观点),但你可以在程序终止之前在 main() 的末尾完成所有操作。或者,让操作系统为你做这件事(如果不是全部的话,大多数人都会这样做。)但这又是一个极端——当你调用 malloc() 时,对象就会存在,当你的程序终止时出去。

    我不应该在这里详细讨论实际含义:内存是有限的。在内存不足之前,您只能分配这么多字节。对所有东西都使用 static 对象太浪费了;尝试重用大块或一大块 static 内存会很困难,并且在任何情况下都类似于动态分配方法。对长寿命对象使用 自动 存储将迫使您将它们的范围尽可能大,无论如何大致对应于 静态 对象的范围。

    --

    现在,一些注意事项:

    {
        int *temp = malloc(sizeof(int));
        *temp = 5;
        //free(temp);
    }
    

    注意这里的temp 是一个自动对象。它只会在它的作用域内存活,它以 } 结尾。然而,它指向的对象 已被分配。它会一直存在,直到您在其地址上调用 free()。由于temp 包含该地址的唯一副本,因此一旦temp 超出范围,您将失去调用free() 的机会。一些内存将被永久分配,但不可用。这称为内存泄漏

    垃圾收集是另一种管理对象存储的方法。 C 中的实现可能如下所示:

    {
        int *temp = gc_malloc(sizeof(int));
        *temp = 5;
    }
    

    在 } 处,计算机将确定对已分配对象的最后一个引用 temp 丢失了,最好释放它。

    这是一种权衡,您不必担心释放对象(这不是一件小事,因为简单的示例可能会让您想到),但是这里的 gc_malloc() 比简单的 malloc(),并且在 temp 超出范围的 } 处执行了不可见的代码。计算机如何决定temp 是最后一个引用,这是一个完全不同的话题。 (一些实用的解决方案可能需要您围绕“int *temp”编写更多代码。)

    【讨论】:

      【解决方案5】:

      Alnitak 是正确的。我想指出“stack”的真正含义。

      当程序调用函数时,它希望函数完成一些工作,然后返回并继续下一行代码。当函数完成时,函数无法知道返回到哪里。因此,机器对每个程序都有一个调用栈,用于在调用函数之前将程序中以下语句的地址压入。 “return”语句只是简单地弹出程序地址并跳转到它。

      堆栈也是一个方便的临时空间便签本。可以写入堆栈的未使用区域。在 C 函数中声明一个局部变量就是这样做的。当函数返回时,堆栈不需要清理、释放或以其他方式处理,因为它只是一个临时空间,现在超出了范围。

      相比之下,调用malloc() 会从堆中分配内存,这会显式地为程序留出内存并在程序运行期间一直保持在作用域内。因此,如果您不free() 内存,它将保持分配状态并被视为内存泄漏。

      【讨论】:

        【解决方案6】:

        free() 的需要并不取决于你是否声明了一个指针,而是你是否有malloc()ed 内存。

        就像 Brian Bondy 之前所说的,变量(“int number”、“char string[10]”、“float your_boat”等)会在超出范围时消失,例如当您的代码离开功能块时。因此,当您调用 free() 时,您的问题中的指针 ("temp") 不会消失 - 相反,无论您在调用 malloc() 时分配的代码都消失了。您的指针仍然停留在那里,即在您的示例代码之后您可以立即说“temp = &some_other_variable”而不必(再次)说“int *temp;”。

        如果有人实现了一个函数,他们也碰巧调用了malloc(),它会为你的程序申请内存,并且不需要你释放这些数据,那么你会会说

        int * temp = (int*)malloc(sizeof(int));
        

        事不宜迟

        free(temp);
        

        但这不是malloc() 的实现方式。

        【讨论】:

          【解决方案7】:

          进一步指出this post 进一步澄清了事情。

          【讨论】:

            【解决方案8】:

            这是一个很好的问题,虽然很多人会回答这是堆栈和堆分配之间的区别,但基本的答案是底层系统向您暴露了一些不应该的东西。

            当您分配内存时,您不必担心将其归还。系统应该足够聪明,可以判断出您不再可以访问(指向或引用)它,因此它可以自动收回内存。

            Java 和 C# 等较新的语言已经做到了这一点。

            【讨论】:

            • 这是有争议的,没有回答问题。它也与语言的年龄无关(请参阅 Lisp 作为具有垃圾收集的旧语言的示例)。
            • 我认为“当你分配内存时,你不应该担心归还它”的说法非常幼稚。我会说你应该〜总是〜担心将其归还,即使您的环境包括垃圾收集。
            • 争论?我只是给出了更高层次的答案
            • 您做出了重大假设。例如,系统程序员绝对应该担心内存分配和释放。
            • 即使在 Java 和 C# 等语言中,您也必须担心将内存归还。只是你要担心的问题发生了变化。是的,答案大多毫无意义,相当于一场解压缩和比较比赛。
            猜你喜欢
            • 1970-01-01
            • 2014-02-17
            • 1970-01-01
            • 2016-11-08
            • 2021-03-20
            • 2013-12-17
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多