当程序终止前没有释放malloc分配的内存,会发生什么?

654
我们都被教导必须释放每个分配的指针。然而我有点好奇,如果不释放内存会有什么实际代价。在一些明显的情况下,比如在循环或线程执行的一部分中调用 malloc() 时,释放内存非常重要以避免内存泄漏。但是考虑以下两个示例:
首先,如果我的代码类似于这样:
int main()
{
    char *a = malloc(1024);
    /* Do some arbitrary stuff with 'a' (no alloc functions) */
    return 0;
}

这里的真正结果是什么?我的想法是,进程会死亡,然后堆空间也会消失,因此错过调用free并没有什么损失(但是,我认识到为了闭包、可维护性和良好的实践,还是有必要的)。我的想法正确吗?

其次,假设我有一个类似于shell的程序。用户可以像这样声明变量:aaa = 123,并将其存储在一些动态数据结构中以供以后使用。显然,你会使用一些调用某些*alloc函数的解决方案(哈希表、链表等)。对于这种类型的程序,从malloc调用后永远不释放它们是没有意义的,因为这些变量在程序执行期间始终存在,并且我无法找到好的方法(静态分配空间的话)来实现这一点。拥有大量已分配但仅在进程结束时释放内存的做法是糟糕的设计吗?如果是,那么替代方案是什么?


34
以下是翻译的结果: 以下人们经常说一个好的现代操作系统可以进行清理,但如果代码运行在内核模式下(例如出于性能原因),该怎么办? 内核模式程序(例如Linux中)是否被沙箱化了? 如果没有,我认为你需要手动释放所有内容,即使在类似abort()的异常终止之前。 - SO Stinks
11
@Dr.PersonPersonII,是的,运行在内核模式下的代码通常需要手动释放所有资源。 - zwol
6
我想补充一下,free(a)并不能真正释放内存!它只是重置了libc实现的malloc中一些指针,这些指针跟踪内存页(通常称为“堆”)中可用的内存块。当你的程序终止时,该内存页才会被释放,而不是在此之前。 - Marco Bonelli
2
@MarcoBonelli 部分正确。如果malloc()分配的内存来自“正常”的sbrk堆,并且位于其末尾,则会调用sbrk()来减少内存映像。如果malloc()通过mmap()分配了内存,则在free()中取消映射该内存。 - glglgl
2
@SOStinks 内核“程序”不像用户空间程序那样退出。内核代码“存在”的等效方式是系统关闭,在这种情况下,您不需要释放它。只是在内核中,您通常运行的时间很长,以至于几乎没有情况下您不需要释放内存。 - forest
显示剩余4条评论
20个回答

7
实际上,在操作系统本科课程的 OSTEP在线教材中,有一个章节正是讨论了你的问题。相关章节在第6页的 Memory API 章节中,名为“忘记释放内存”,其中给出了以下解释:
“在某些情况下,似乎不调用free()是合理的。例如,您的程序生命周期很短,很快就会退出;在这种情况下,当进程死亡时,操作系统将清除其分配的所有页面,并且因此不会发生内存泄漏。虽然这当然“有效”(请参见第7页的旁注),但可能是一种不好的习惯,因此要谨慎选择这种策略。”
这段摘录是在介绍虚拟内存概念的背景下。基本上,在书中的这一点上,作者解释了操作系统的目标之一是“虚拟化内存”,即让每个程序相信它具有访问非常大的内存地址空间的权限。
在幕后,操作系统将用户看到的“虚拟地址”转换为指向物理内存的实际地址。
然而,共享资源(例如物理内存)需要操作系统跟踪哪些进程正在使用它。因此,如果一个进程终止,则操作系统有能力并且设计目标是回收该进程的内存,以便可以重新分配和共享内存给其他进程。

编辑:摘录中提到的旁注如下所示。

ASIDE: 为什么进程退出后不会泄漏任何内存
当您编写短暂的程序时,可能会使用 malloc() 分配一些空间。程序运行并即将完成:在退出之前需要调用多次 free() 吗?虽然不这样做似乎是错误的,但实际上不会“丢失”任何内存。原因很简单:系统中真正有两个级别的内存管理。第一个级别的内存管理由操作系统执行,它在进程运行时向进程分配内存,在进程退出(或死亡)时收回内存。第二个级别的管理在每个进程内部进行,例如在堆内调用 malloc() 和 free() 时。即使您未调用 free()(从而在堆内泄漏内存),操作系统也会在程序完成运行时回收进程的所有内存(包括代码、堆栈以及与此相关的堆)。无论您的地址空间中堆的状态如何,操作系统都会在进程终止时收回所有这些页面,从而确保尽管您没有释放它,也不会丢失任何内存。
因此,对于短暂的程序,泄漏内存通常不会导致任何操作问题(虽然可能被认为是不良形式)。当您编写长时间运行的服务器(例如 Web 服务器或数据库管理系统,永不退出)时,泄漏的内存是一个更大的问题,并且最终会导致应用程序耗尽内存而崩溃。当然,在特定程序内部泄漏内存是一个更大的问题:操作系统本身。再次向我们展示:编写内核代码的人工作最艰难...
引自《操作系统:三个容易部分》Operating Systems: Three Easy Pieces第7页Memory API章节 Remzi H. Arpaci-Dusseau 和 Andrea C. Arpaci-Dusseau Arpaci-Dusseau Books 2015年3月(版本0.90)

5

不释放变量并不会带来真正的 危险,但如果你将指向一块内存块的指针分配给另一块内存块而不释放第一块内存,则第一块内存将不再可访问,但仍占用空间。这就是所谓的内存泄漏,如果您经常这样做,则进程将开始消耗越来越多的内存,从而夺取系统资源,影响其他进程。

如果进程的生命周期很短,则通常可以通过这样做来获得好处,因为当进程完成时,操作系统会回收所有分配的内存。但我建议养成释放所有不再需要的内存的习惯。


1
我想对你的第一句话“没有危险”说-1,除非你随后给出一个深思熟虑的答案来解释为什么确实存在危险。 - DevinB
3
危险程度相对较轻——与其遇到段错误,我宁愿面对内存泄漏。 - Kyle Cronin
1
非常正确,而我们两个都不愿意选择任何一个 =D - DevinB
2
@KyleCronin 我宁愿遇到段错误也不想遇到内存泄漏,因为两者都是严重的错误,而且段错误更容易被检测出来。由于内存泄漏“相当温和”,所以往往会被忽视或未解决。但我和我的 RAM 完全不同意这种说法。 - Dan Bechard
1
作为一名开发人员,当然可以。但作为用户,我会选择内存泄漏。我宁愿使用能够工作的软件,即使会有内存泄漏,也不愿使用不能工作的软件。 - Kyle Cronin
显示剩余2条评论

4

这取决于你正在处理的项目范围。就你的问题而言,也就是说仅仅针对你的问题,那么无所谓。

以下是一些场景说明(可选):

(1) - 如果你正在嵌入式环境中工作,不能依赖主操作系统来为你回收内存,则应该释放它们,因为内存泄漏如果不经意间发生,可能会导致程序崩溃。

(2) - 如果你正在进行个人项目开发,不打算向其他人公开,那么你可以跳过它(假设你在主操作系统中使用),或者出于“最佳实践”考虑将其包含在内。

(3) - 如果你正在进行项目开发,并计划开源,那么你需要更多地了解你的受众群体,并确定释放内存是否是更好的选择。

(4) - 如果你有一个大型库,受众只包括主要操作系统,那么你不需要释放它,因为他们的操作系统将帮助他们这样做。同时,通过不释放内存,你的库/程序可以帮助提高整体性能,因为程序不必关闭每个数据结构,从而延长关机时间(想象一下在离开房子前,非常缓慢而痛苦地等待计算机关机...)

我可以继续说明应该采取哪种方式,但最终取决于你的程序想要实现什么。在某些情况下,释放内存被认为是良好的做法,在某些情况下则不然,因此最终取决于你所处的具体情况,并在适当的时间询问正确的问题。祝好运!


一些重要的开源项目(包括GCC编译器)并不费心释放所有动态分配的内存。我实事求是地认为他们这样做是正确的(因为操作系统内核会在进程终止时恢复它)。 - Basile Starynkevitch

3

您是正确的,当进程退出时,内存会自动释放。有些人在进程终止时努力不进行大量清理,因为所有内容都将被释放给操作系统。然而,在程序运行时,您应该释放未使用的内存。如果不这样做,您可能最终会耗尽内存或导致过度分页,如果您的工作集变得太大。


3
您的看法是完全正确的。在小型琐碎的程序中,如果一个变量必须一直存在到程序结束,那么释放内存就没有真正的好处。
事实上,我曾经参与过一个项目,每次执行程序都非常复杂但相对短暂,决定只保留内存分配,并避免因错误释放内存而使项目不稳定。
话虽如此,在大多数程序中,这并不是一个真正的选择,否则会导致内存用尽。

2
如果一个程序在退出之前忘记释放几兆字节,操作系统会自动释放它们。但是,如果你的程序连续运行数周,并且其中一个循环在每次迭代中都忘记释放一些字节,那么你就会遇到一个巨大的内存泄漏问题,除非你定期重新启动计算机,否则它将耗尽所有可用内存=>即使最初并不是为重大任务设计的,但如果程序用于重大任务,即使是小型内存泄漏也可能是不好的。

如果你的服务器程序每次服务时都会泄漏内存,那么最终会耗尽内存。对于只运行一次的程序来说,这不是什么大问题。但对于全天候运行的程序来说,这是一个大问题。从我的角度来看,如果你不释放资源,那么你就不能被雇用。 - EvilTeach

2
如果您从头开始开发应用程序,您可以在何时调用free方面做出一些明智的选择。您的示例程序很好:它分配内存,也许您让它工作了几秒钟,然后关闭并释放所有已声明的资源。
但是,如果您正在编写其他内容 - 服务器/长时间运行的应用程序或要由他人使用的库,则应该期望在每个malloc上调用free。
忽略实用方面,遵循更严格的方法更加安全,并强制自己释放您malloc的所有内容。如果您不习惯在编码时观察内存泄漏,您可能会轻易地泄漏一些内存。因此,换句话说,是的 - 您可以不使用它;请小心。

1

这取决于程序运行的操作系统环境,正如其他人已经指出的那样,对于长时间运行的进程,释放内存并避免即使是非常缓慢的泄漏也很重要。但是,如果操作系统处理东西,例如Unix自从很久以前就一直在做的那样,那么您不需要释放内存,也不需要关闭文件(内核在进程退出时关闭所有打开的文件描述符)。 如果您的程序分配了大量内存,甚至可能有益于毫不犹豫地退出。我发现当我退出Firefox时,它会花费几分钟的时间在许多进程中分页数千兆字节的内存。我想这是由于必须调用C++对象上的析构函数。这实际上是可怕的。有些人可能会争辩说,这是为了一致地保存状态,但在我看来,像浏览器、编辑器和设计程序等长时间运行的交互式程序应确保任何状态信息、首选项、打开的窗口/页面、文档等都经常写入永久存储器,以避免在崩溃时丢失工作。然后,当用户选择退出并完成时,可以快速执行此状态保存,并立即退出进程。


0

所有为该进程分配的内存将被操作系统标记为未使用,然后被重用,因为内存分配是由用户空间函数完成的。

想象操作系统是一个上帝,而内存是创建进程世界的材料。上帝使用一些材料创造了一个世界(或者说操作系统保留了一些内存并在其中创建了一个进程)。无论这个世界中的生物做了什么,不属于这个世界的材料都不会受到影响。在这个世界到期之后,上帝操作系统可以回收为这个世界分配的材料。

现代操作系统可能在释放用户空间内存方面有不同的细节,但这是操作系统的基本职责。


问题的标签是“c malloc free”。因此,你的答案是错误的,因为可能根本没有操作系统。请参考dhein的正确答案:https://dev59.com/_3RB5IYBdhLWcg3wV156#30118469。已经有很多答案了,你的答案没有提供新的细节。请查看[如何回答](https://stackoverflow.com/help/how-to-answer)。 - reichhart

-2

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

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接