当你在malloc之后没有自由时,真正发生了什么?

时间:2009-03-17 15:29:09

标签: c malloc free

这一直困扰着我多年。

我们都在学校(至少,我是)教过你必须释放每个分配的指针。不过,我有点好奇,关于不释放内存的实际成本。在一些明显的情况下,比如在循环内部或线程执行的一部分中调用malloc时,释放是非常重要的,因此没有内存泄漏。但请考虑以下两个例子:

首先,如果我的代码是这样的:

int main()
{
    char *a = malloc(1024);
    /* Do some arbitrary stuff with 'a' (no alloc functions) */
    return 0;
}

这里真正的结果是什么?我的想法是,进程终止然后堆空间无论如何都没有了,因此错过调用free没有任何害处(但是,我确实认识到无论如何都需要关闭,可维护性和良好实践) 。我对这个想法是对的吗?

其次,假设我的程序有点像shell。用户可以声明aaa = 123之类的变量,并将这些变量存储在某些动态数据结构中供以后使用。很明显,你可以使用一些解决方案来调用一些* alloc函数(hashmap,链表,类似的东西)。对于这种程序,在调用malloc之后永远自由是没有意义的,因为这些变量必须在程序执行期间始终存在,并且没有好的方法(我可以看到)实现它静态分配的空间。拥有一堆已分配但仅作为流程结束的一部分释放的内存,这是不好的设计吗?如果是这样,有什么替代方案?

18 个答案:

答案 0 :(得分:341)

几乎每个现代操作系统都会在程序退出后恢复所有已分配的内存空间。我能想到的唯一例外可能是Palm OS,程序的静态存储和运行时内存几乎是一样的,所以不释放可能会导致程序占用更多存储空间。 (我只是在这里推测。)

所以一般情况下,除了拥有超出您需要的存储空间的运行时成本之外,它没有任何损害。当然,在你给出的例子中,你想保留一个可能被使用的变量的内存,直到它被清除。

然而,只要你不再需要它就可以释放内存,并且在程序退出时释放你仍然拥有的任何东西。这更像是一种了解你正在使用的记忆,并思考你是否仍然需要它的练习。如果不跟踪,可能会发生内存泄漏。

另一方面,在退出时关闭文件的类似告诫具有更具体的结果 - 如果不这样做,您写入的数据可能无法刷新,或者如果它们是临时文件,完成后,它们可能不会被删除。此外,数据库句柄应提交其事务,然后在完成它们时关闭。类似地,如果您使用的是面向对象的语言(如C ++或Objective C),那么在完成对象时不释放对象将意味着析构函数永远不会被调用,并且类负责的任何资源都可能无法清除。

答案 1 :(得分:104)

是的,你是对的,你的例子不会造成任何伤害(至少在大多数现代操作系统上都没有)。进程退出后,操作系统将恢复进程分配的所有内存。

来源:Allocation and GC Myths(PostScript提醒!)

  

分配误区4:非垃圾收集程序   应该总是释放所有的记忆   他们分配。

     

真相:省略   经常执行的解除分配   代码导致泄漏增加。他们是   很少接受。但程序   保留最多分配的内存,直到   程序退出通常表现更好   没有任何干预释放。   如果,Malloc更容易实现   没有免费的。

     

在大多数情况下,释放内存   在程序退出之前没有意义。   操作系统无论如何都会收回它。自由   将触摸和页面死亡   对象;操作系统不会。

     

结果:小心“泄漏   探测器“计算分配。   一些“泄漏”是好的!

那就是说,你应该尽量避免所有内存泄漏!

第二个问题:你的设计还可以。如果您需要在应用程序退出之前存储某些内容,那么可以通过动态内存分配来执行此操作。如果您不知道所需的大小,则不能使用静态分配的内存。

答案 2 :(得分:52)

=== 面向未来代码重用怎么样? ===

如果您编写代码来释放对象,那么您可以将代码限制为只有在依赖于进程正在释放的内存时才能安全使用关闭...即小型一次性使用项目或“扔掉” [1] 项目)......您知道该过程何时结束。

如果您执行编写free()s所有动态分配内存的代码,那么您将来可以验证代码并让其他人在更大的项目中使用它。


[1]关于“丢弃”项目。 “投掷”项目中使用的代码有一种不被丢弃的方式。接下来你知道十年过去了,你的“扔掉”代码仍在使用中。

我听到一个关于一些人为了让他的硬件更好地工作而编写一些代码的故事。他说“just a hobby, won't be big and professional”。多年以后,很多人都在使用他的“爱好”代码。

答案 3 :(得分:47)

你是对的,没有伤害,退出

更快

这有多种原因:

  • 所有桌面和服务器环境都只是在exit()上释放整个内存空间。他们不了解程序内部数据结构,例如堆。

  • 几乎所有free()实现都不会将内存返回给操作系统。

  • 更重要的是,在exit()之前完成时浪费时间。在退出时,简单地释放内存页面和交换空间。相比之下,一系列free()调用会占用CPU时间,并可能导致磁盘分页操作,缓存未命中和缓存驱逐。

关于未来代码重用的 possiblility ,证明无意义操作的确定性:这是一个考虑因素,但它可能不是Agile方式。 YAGNI!

答案 4 :(得分:22)

一旦我确定我已完成它,我通常会释放每个已分配的块。今天,我的程序的入口点可能是main(int argc, char *argv[]),但明天它可能是foo_entry_point(char **args, struct foo *f)并输入为函数指针。

所以,如果发生这种情况,我现在有泄漏。

关于你的第二个问题,如果我的程序输入a = 5,我会为a分配空间,或者在随后的a =“foo”上重新分配相同的空间。这将保持分配,直到:

  1. 用户输入'unset a'
  2. 输入了我的清理功能,无论是维修信号还是用户输入'quit'
  3. 我想不出任何现代操作系统在进程退出后不回收内存。然后,free()便宜,为什么不清理?正如其他人所说,像valgrind这样的工具非常适合发现你确实需要担心的漏洞。即使您的示例块被标记为“仍然可以访问”,但当您尝试确保没有泄漏时,它只会在输出中产生额外的噪音。

    另一个神话是“如果它在main()中,我不必释放它”,这是不正确的。请考虑以下事项:

    char *t;
    
    for (i=0; i < 255; i++) {
        t = strdup(foo->name);
        let_strtok_eat_away_at(t);
    }
    

    如果在分叉/守护之前(理论上永远运行),你的程序刚刚泄漏了一个不确定大小的t 255次。

    一个好的,写得很好的程序应该总是自己清理。释放所有内存,清除所有文件,关闭所有描述符,取消所有临时文件的链接等。这个清除功能应该在正常终止时或在收到各种致命信号时到达,除非你想留下一些文件,这样你就可以检测到崩溃并恢复。

    真的,要善待那些在你继续做其他事情时必须保持你的东西的可怜的灵魂。把它交给他们'valgrind clean':)

答案 5 :(得分:21)

我完全不同意那些说OP正确或没有伤害的人。

每个人都在谈论现代和/或传统的操作系统。

但是如果我在一个没有操作系统的环境中怎么办? 什么都没有?

想象一下,现在您正在使用线程样式的中断并分配内存。 在C标准ISO / IEC:9899中,内存的生命周期表示为:

  

7.20.3内存管理功能

     

1通过连续调用calloc分配的存储的顺序和连续性,   malloc和realloc函数未指定。如果分配则返回指针   适当地对齐成功,以便可以将其指定给指向任何类型对象的指针   然后用于在分配的空间中访问此类对象或此类对象的数组   (直到空间被明确解除分配)。分配对象的生命周期延长   从分配到解除分配。[...]

所以不能说环境正在为你做免费工作。 否则它将被添加到最后一句:&#34;或者直到程序终止。&#34;

换句话说: 不释放记忆不仅仅是不好的做法。它产生非便携而不符合C的代码。 至少可以将其视为正确的,如果以下情况:[...],则受到环境的支持。

但是在你根本没有操作系统的情况下,没有人为你做这项工作 (我知道你通常不会在嵌入式系统上分配和重新分配内存,  但有些情况下你可能想要。)

所以一般说来是普通的C(标记了OP),  这只是产生错误的和不可移植的代码。

答案 6 :(得分:12)

退出时留下不准确的内存是完全没问题的; malloc()从称为“堆”的内存区域分配内存,并在进程退出时释放进程的完整堆。

话虽如此,人们仍然坚持认为在退出之前释放所有东西是好的一个原因是内存调试器(例如Linux上的valgrind)将不可用的块检测为内存泄漏,如果你还有“真正的”内存泄漏如果你最后得到“假的”结果,那么发现它们就变得更加困难了。

答案 7 :(得分:11)

如果您正在使用已分配的内存,那么您没有做错任何事情。当您编写在不释放内存的情况下分配内存的函数(除了main之外)并且不将其提供给程序的其余部分时,它就会成为一个问题。然后你的程序继续运行分配给它的内存,但无法使用它。你的程序和其他正在运行的程序被剥夺了内存。

编辑:说其他正在运行的程序被剥夺了内存并不是100%准确。操作系统总是允许他们使用它,代价是将程序交换到虚拟内存(</handwaving>)。但重点是,如果你的程序释放了它没有使用的内存,则不太可能需要虚拟内存交换。

答案 8 :(得分:11)

此代码通常可以正常工作,但请考虑代码重用的问题。

您可能已经编写了一些代码片段,它不会释放已分配的内存,但它的运行方式会自动回收内存。似乎没事。

然后其他人将您的代码段复制到他的项目中,使其每秒执行一千次。那个人现在在他的程序中有一个巨大的内存泄漏。一般来说不是很好,通常对服务器应用程序是致命的。

代码重用在企业中很常见。通常,公司拥有其员工生产的所有代码,每个部门都可以重复使用公司拥有的任何代码。因此,通过编写这种“天真无邪”的代码,您可能会对其他人造成潜在的麻烦。这可能会让你被解雇。

答案 9 :(得分:5)

  

这里真正的结果是什么?

您的程序泄露了内存。根据您的操作系统,可能已恢复。

大多数现代桌面操作系统 在进程终止时恢复泄露的内存,这使得忽略此问题变得非常常见,这可以从许多其他答案中看到。)

但是你依赖的是一个你不应该依赖的安全功能,你的程序(或函数)可能会在这个行为 导致“硬”内存泄漏的系统上运行,<强>下一次时间。

您可能正在内核模式下运行,或者在不使用内存保护作为权衡的老式/嵌入式操作系统上运行。 (MMU占用芯片空间,内存保护需要额外的CPU周期,并且要求程序员自行清理后也不会太多。)

您可以按照自己喜欢的方式使用和重复使用内存,但请确保在退出之前取消分配所有资源。

答案 10 :(得分:4)

实际上,OSTEP操作系统本科课程的在线教科书中有一节可以准确地讨论您的问题。

相关部分是&#34;忘记释放记忆&#34;在第6页的Memory API chapter中给出了以下解释:

  

在某些情况下,似乎没有调用free()是合理的。对于   例如,你的程序是短暂的,很快就会退出; 在这种情况下,   当进程终止时,操作系统将清理其所有已分配的页面   因此本身不会发生内存泄漏。 虽然这肯定“有效”   (见第7页的旁边),这可能是一个坏习惯,所以要小心谨慎   选择这样的策略

此摘录是在介绍虚拟内存概念的背景下。基本上在本书的这一点上,作者解释说操作系统的目标之一是“虚拟化内存”,#34;也就是说,让每个程序都相信它可以访问一个非常大的内存地址空间。

在幕后,操作系统将翻译虚拟地址&#34;用户看到指向物理内存的实际地址。

但是,共享物理内存等资源需要操作系统跟踪正在使用它的进程。因此,如果一个进程终止,那么操作系统的功能和设计目标就是回收进程的内存,以便它可以与其他进程重新分配和共享内存。

编辑:摘录中提到的旁边复制如下。

  

ASIDE:为什么在您的流程退出时没有记忆泄漏

     

当你编写一个短命的程序时,你可能会分配一些空间   使用malloc()。程序运行并即将完成:是否存在   需要在退出之前多次拨打free()?虽然看来   错了没有,没有记忆会失去&#34;在任何真正的意义上。原因是   简单:系统中实际上有两个级别的内存管理。   第一级内存管理由OS执行   在运行时将内存分发给进程,并在运行时将其取回   进程退出(或以其他方式死亡)。第二级管理   在每个进程中,例如在您调用时在堆内   malloc()free()。即使你没有打电话给free()(因而泄密   在堆内存中,操作系统将回收所有内存   过程(包括代码,堆栈的页面,以及相关的页面,   堆)程序运行完毕后。无论什么状态   在你的地址空间堆中,操作系统会收回所有这些页面   当进程死亡时,确保尽管没有内存丢失   事实上,你没有释放它。

     

因此,对于短命的程序,泄漏的内存通常不会导致任何问题   操作问题(尽管可能被视为不良形式)。什么时候   你编写一个长期运行的服务器(如Web服务器或数据库管理)   系统,永不退出),泄露内存是一个更大的问题,   当应用程序耗尽时,最终会导致崩溃   记忆。当然,内存泄漏是一个更大的问题   一个特定的程序:操作系统本身。向我们展示一次   再次:那些编写内核代码的人有最艰难的工作......

     
    来自

Memory API章第7页的

         

<强> Operating Systems: Three Easy Pieces
    Remzi H. Arpaci-Dusseau和Andrea C. Arpaci-Dusseau     Arpaci-Dusseau书籍     2015年3月(版本0.90)

  

答案 11 :(得分:3)

在这方面你是完全正确的。在程序死亡之前必须存在变量的小琐碎程序中,释放内存没有任何实际好处。

事实上,我曾经参与过一个项目,每个程序的执行都非常复杂但相对来说很短暂,而且决定只是保持内存分配而不是通过错误解除分配来破坏项目的稳定性。

话虽如此,在大多数程序中,这不是一个真正的选择,或者它可能导致你的内存耗尽。

答案 12 :(得分:3)

没有真正的危险没有释放你的变量,但是如果你将一块内存块的指针分配给不同的内存块而不释放第一个块,那么第一个块就不再可访问了但仍占用空间。这就是所谓的内存泄漏,如果你定期执行此操作,那么你的进程将开始消耗越来越多的内存,从其他进程中夺走系统资源。

如果进程是短暂的,你可以经常这样做,因为当进程完成时,操作系统会回收所有已分配的内存,但我建议养成释放所有内存的习惯,不再使用对

答案 13 :(得分:2)

你是对的,进程退出时会自动释放内存。有些人在流程终止时努力不进行大量清理,因为它将全部放弃到操作系统。但是,在程序运行时,您应释放未使用的内存。如果不这样做,如果工作集太大,最终可能会耗尽或导致过多的分页。

答案 14 :(得分:2)

如果您正在从头开发应用程序,您可以做出一些有关何时免费通话的有根据的选择。你的示例程序很好:它分配内存,也许你让它工作几秒钟,然后关闭,释放它声称的所有资源。

如果您正在编写其他任何内容 - 服务器/长时间运行的应用程序或其他人使用的库,您应该期望在malloc的所有内容上免费使用。

忽略实用方面一秒钟,遵循更严格的方法更安全,并强迫自己释放你malloc的一切。如果您不习惯在编码时观察内存泄漏,则可能会轻易引发一些泄漏。换句话说,是的 - 没有它你就可以逃脱;不过请小心。

答案 15 :(得分:0)

如果程序在退出之前忘记释放几兆字节,则操作系统将释放它们。但是,如果您的程序一次运行数周,并且程序中的循环忘记每次迭代都释放几个字节,则可能会发生巨大的内存泄漏,除非您定期重新启动它,否则它将吞噬计算机中的所有可用内存。基础=>即使程序最初用于某个大型任务,即使很小的内存泄漏也可能是不好的。

答案 16 :(得分:0)

这取决于您正在处理的项目的范围。就您的问题而言,我的意思只是您的问题,那没关系。

对于进一步的解释(可选),我从整个讨论中注意到的一些情况如下:

(1)-如果您在无法依靠主OS回收内存的嵌入式环境中工作,则应释放它们,因为如果不注意内存泄漏实际上会导致程序崩溃。 / p>

(2)-如果您正在从事一个个人项目,而不会将其透露给其他人,则可以跳过它(假设您在主操作系统上使用它),或者将其包含在“最佳做法”。

(3)-如果您正在开发一个项目并计划将其开源,那么您需要对受众进行更多研究,并确定释放内存是否是更好的选择。

(4)-如果您的图书馆很大,并且观众仅由主操作系统组成,那么您就无需释放它,因为他们的操作系统将帮助他们实现这一目标。同时,由于不释放空间,您的库/程序可能有助于使整体性能更突如其来,因为该程序不必关闭每个数据结构,从而延长了关闭时间(想象一个非常缓慢的耗费时间的等待,直到您离开计算机之前关闭计算机房子...)

我可以继续指定要修的课程,但这最终取决于您要通过程序实现的目标。在某些情况下,释放内存被认为是一种好习惯,而在某些情况下则不那么重要,因此释放内存最终取决于您所处的特定情况并在正确的时间提出正确的问题。祝你好运!

答案 17 :(得分:-2)

我认为你的两个例子实际上只有一个:free()只应在流程结束时发生,正如你所指出的那样,因为流程正在终止,所以它是无用的。

在第二个示例中,唯一的区别是您允许未定义的malloc()个数,这可能会导致内存不足。处理这种情况的唯一方法是检查malloc()的返回码并采取相应的行动。