提问人:Scott 提问时间:3/17/2009 最后编辑:user438383Scott 更新时间:8/16/2023 访问量:170289
当您在程序终止之前没有释放 malloc 时,会发生什么?
What REALLY happens when you don't free after malloc before program termination?
问:
我们都被教导说,你必须释放分配的每个指针。不过,我有点好奇不释放内存的真正代价。在一些明显的情况下,例如在循环或线程执行的一部分中调用时,释放非常重要,这样就不会发生内存泄漏。但请考虑以下两个示例:malloc()
首先,如果我有代码,那是这样的:
int main()
{
char *a = malloc(1024);
/* Do some arbitrary stuff with 'a' (no alloc functions) */
return 0;
}
这里真正的结果是什么?我的想法是,进程死了,然后堆空间无论如何都消失了,所以错过调用并没有什么坏处(但是,我确实认识到无论如何拥有它对于闭合、可维护性和良好实践的重要性)。我的想法是对的吗?free
其次,假设我有一个程序,它有点像 shell。用户可以声明变量,例如,这些变量存储在一些动态数据结构中以备后用。显然,很明显,你会使用一些解决方案来调用一些 *alloc 函数(哈希图、链表等)。对于这种程序,在调用后释放是没有意义的,因为在程序执行期间,这些变量必须始终存在,并且没有好的方法(我可以看到)使用静态分配的空间来实现这一点。有一堆内存被分配,但只是作为进程结束的一部分释放,这是糟糕的设计吗?如果是这样,还有什么替代方案?aaa = 123
malloc
答:
是的,你是对的,你的例子不会造成任何伤害(至少在大多数现代操作系统上不会)。进程退出后,操作系统将恢复进程分配的所有内存。
来源:Allocation 和 GC Myths(PostScript 警报!
分配误区 4:非垃圾回收程序 应始终释放所有内存 他们分配。
真相:省略 经常执行的解除分配 代码导致泄漏越来越多。他们是 很少可以接受。但是程序 保留大部分已分配的内存,直到 程序退出通常性能更好 没有任何干预的解除分配。 如果出现以下情况,Malloc 更容易实现 没有免费的。
在大多数情况下,解除分配内存 在程序退出之前是没有意义的。无论如何,操作系统都会回收它。自由 将触摸和翻页在死里 对象;操作系统不会。
后果:小心“泄漏 检测器“,用于计算分配。 一些“泄漏”是好的!
也就是说,您真的应该尽量避免所有内存泄漏!
第二个问题:你的设计没问题。如果您需要在应用程序退出之前存储某些内容,那么可以使用动态内存分配来执行此操作。如果预先不知道所需的大小,则无法使用静态分配的内存。
评论
malloc()
exit()
abort()
_Exit()
free()
free()
几乎每个现代操作系统都会在程序退出后恢复所有分配的内存空间。我能想到的唯一例外可能是像 Palm OS 这样的东西,其中程序的静态存储和运行时内存几乎是一回事,因此不释放可能会导致程序占用更多存储空间。(我只是在这里推测。
因此,一般来说,它没有什么坏处,除了存储空间超出所需空间的运行时成本。当然,在你给出的示例中,你希望保留一个变量的内存,直到它被清除为止。
但是,一旦您不再需要内存,并释放您在程序退出时仍然存在的任何内容,这被认为是一种很好的风格。这更像是一种练习,了解您正在使用什么内存,并考虑是否仍然需要它。如果不跟踪,则可能会发生内存泄漏。
另一方面,在退出时关闭文件的类似告诫有一个更具体的结果 - 如果你不这样做,你写给它们的数据可能不会被刷新,或者如果它们是临时文件,它们可能不会在你完成后被删除。此外,数据库句柄应提交其事务,然后在完成处理后关闭。同样,如果您使用的是面向对象的语言,如 C++ 或 Objective C,则在完成对象后不释放对象将意味着析构函数永远不会被调用,并且类负责的任何资源可能不会被清理。
评论
exit
exit
main
不释放变量并没有真正的危险,但是如果将指向内存块的指针分配给另一个内存块而不释放第一个块,则第一个块将不再可访问,但仍会占用空间。这就是所谓的内存泄漏,如果你有规律地这样做,那么你的进程将开始消耗越来越多的内存,从其他进程中夺走系统资源。
如果该过程是短暂的,您通常可以这样做,因为当该过程完成时,所有分配的内存都会作系统回收,但我建议养成释放所有内存的习惯,您没有进一步使用。
评论
退出时保持内存未释放是完全可以的;malloc() 从称为“堆”的内存区域分配内存,当进程退出时,进程的完整堆被释放。
话虽如此,人们仍然坚持认为在退出之前释放所有内容是件好事的一个原因是内存调试器(例如 Linux 上的 valgrind)将未释放的块检测为内存泄漏,如果您也有“真正的”内存泄漏,如果您最后也得到“假”结果,则更难发现它们。
评论
free
exit
如果你使用的是你分配的内存,那么你没有做错任何事情。当您编写函数(main 除外)时,它会成为一个问题,这些函数分配内存而不释放内存,也没有将其提供给程序的其余部分。然后,您的程序继续运行,并分配了该内存,但无法使用它。您的程序和其他正在运行的程序被剥夺了该内存。
编辑:说其他正在运行的程序被剥夺了该内存并不是100%准确的。操作系统总是可以让他们使用它,但代价是将程序换成虚拟内存()。但是,关键是,如果您的程序释放了它未使用的内存,那么虚拟内存交换就不太可能是必要的。</handwaving>
你是对的,当进程退出时,内存会自动释放。有些人在进程终止时尽量不进行大规模清理,因为它将全部交给操作系统。但是,在程序运行时,应释放未使用的内存。如果不这样做,则在工作集过大时,最终可能会用完或导致过多的分页。
如果您从头开始开发应用程序,则可以就何时免费拨打电话做出一些明智的选择。你的示例程序很好:它分配内存,也许你让它工作几秒钟,然后关闭,释放它声称的所有资源。
但是,如果你正在编写其他任何东西——一个服务器/长时间运行的应用程序,或者一个供其他人使用的库,你应该期望在你所恶意的所有东西上免费调用。
暂且忽略务实的一面,遵循更严格的方法,强迫自己释放你所恶意的一切,要安全得多。如果您不习惯在编码时注意内存泄漏,那么很容易就会出现一些泄漏。所以换句话说,是的——你可以在没有它的情况下逃脱;不过,请小心。
在这方面,你是绝对正确的。在小型琐碎程序中,变量必须存在直到程序死亡,解除分配内存没有真正的好处。
事实上,我曾经参与过一个项目,每次执行程序都非常复杂,但相对来说是短暂的,我的决定是保持内存分配,而不是通过错误释放来破坏项目的稳定性。
话虽如此,在大多数程序中,这并不是一个真正的选项,否则它可能会导致内存不足。
我认为你的两个例子实际上只有一个:应该只在过程结束时发生,正如你所指出的,这是无用的,因为过程正在终止。free()
但是,在第二个示例中,唯一的区别是允许未定义数量的 ,这可能会导致内存不足。处理这种情况的唯一方法是检查 的返回代码并采取相应的行动。malloc()
malloc()
=== 未来证明和代码重用呢?===
如果您不编写代码来释放对象,那么您将代码限制为仅在您可以依赖关闭进程释放的内存时才能安全使用......即小型一次性使用项目或“一次性”[1] 项目)......您知道该过程何时结束。
如果你确实编写了 free() 所有动态分配的内存的代码,那么你就是在为代码的未来辩护,并让其他人在更大的项目中使用它。
[1] 关于“抛弃”项目。“扔掉”项目中使用的代码有一种不被扔掉的方法。接下来你知道十年过去了,你的“丢弃”代码仍在使用)。
我听说过一个故事,说有人写了一些代码,只是为了好玩,让他的硬件更好地工作。他说“只是一种爱好,不会是大而专业的”。多年后,很多人都在使用他的“爱好”代码。
评论
malloc()
free
null
null
这段代码通常可以正常工作,但要考虑代码重用的问题。
您可能编写了一些代码片段,这些代码片段不会释放分配的内存,它的运行方式是自动回收内存。似乎没问题。
然后,其他人将您的代码片段复制到他的项目中,使其每秒执行一千次。那个人现在在他的程序中有一个巨大的内存泄漏。一般不是很好,通常对服务器应用程序来说是致命的。
代码重用在企业中很常见。通常,公司拥有其员工产生的所有代码,每个部门都可以重用公司拥有的任何代码。因此,通过编写这种“看起来无辜”的代码,你可能会给其他人带来麻烦。这可能会让你被解雇。
评论
一旦我确定我完成了它,我通常会释放每个分配的块。今天,我的程序的入口点可能是 ,但明天它可能是,并键入为函数指针。main(int argc, char *argv[])
foo_entry_point(char **args, struct foo *f)
所以,如果发生这种情况,我现在有一个泄漏。
关于你的第二个问题,如果我的程序接受像 a=5 这样的输入,我会为 a 分配空间,或者在随后的 a=“foo” 上重新分配相同的空间。这将保持分配状态,直到:
- 用户键入了“unset a”
- 输入了我的清理功能,要么是处理信号,要么是用户键入“退出”
我想不出任何现代操作系统在进程退出后不会回收内存。话又说回来,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”:)
评论
free() is cheap
除非你有十亿个具有复杂关系的数据结构,你必须一个接一个地释放,否则遍历数据结构以尝试释放所有内容最终可能会大大增加你的关闭时间,特别是如果该数据结构的一半已经分页到磁盘上,没有任何好处。
你是对的,没有造成伤害,退出会更快
造成这种情况的原因有很多:
所有桌面和服务器环境都只是在 exit() 上释放整个内存空间。他们不知道程序内部数据结构,例如堆。
无论如何,几乎所有的实现都不会将内存返回给操作系统。
free()
更重要的是,在 exit() 之前完成是浪费时间。在退出时,内存页和交换空间被简单地释放。相比之下,一系列 free() 调用将消耗 CPU 时间,并可能导致磁盘分页操作、缓存未命中和缓存逐出。
关于未来代码重用的可能性,证明无意义运维的确定性:这是一个考虑因素,但可以说它不是敏捷的方式。雅格尼!
评论
free()
exit()
exit()
我完全不同意所有说OP是正确的或没有伤害的人。
每个人都在谈论现代和/或遗留的操作系统。
但是,如果我所处的环境根本没有操作系统怎么办? 哪里什么都没有?
想象一下,现在您正在使用线程样式的中断并分配内存。 在 C 标准中,ISO/IEC:9899 是内存的寿命,表示为:
7.20.3 内存管理功能
1 连续调用 calloc 所分配的存储的顺序和连续性, malloc 和 realloc 函数未指定。指针返回时分配 succeeds 适当对齐,以便可以将其分配给指向任何类型对象的指针 然后用于访问分配的空间中的此类对象或此类对象的数组 (直到显式解除分配空间)。已分配对象的生存期延长 从分配到解除分配。[...]
因此,不必让环境为你做解放的工作。 否则,它将被添加到最后一句话中:“或者直到程序终止。
换句话说: 不释放内存不仅仅是不好的做法。它生成不可移植且不符合 C 的代码。 这至少可以看作是“正确的,如果以下情况:[...],受环境支持”。
但是在你根本没有操作系统的情况下,没有人为你做这项工作 (我知道您通常不会在嵌入式系统上分配和重新分配内存, 但在某些情况下,您可能希望这样做。
因此,一般来说,普通 C(OP 被标记为), 这只是产生错误且不可移植的代码。
评论
这里真正的结果是什么?
您的程序泄漏了内存。根据您的操作系统,它可能已恢复。
大多数现代桌面操作系统确实会在进程终止时恢复泄漏的内存,因此忽略该问题很可悲(从此处的许多其他答案可以看出这一点)。
但是你所依赖的安全功能不是语言的一部分,你不应该依赖它。您的代码可能会在下次此行为导致“硬”内存泄漏的系统上运行。
您的代码最终可能会在内核模式下运行,或者在不使用内存保护作为权衡的老式/嵌入式操作系统上运行。(MMU 占用芯片空间,内存保护会花费额外的 CPU 周期,要求程序员自己清理并不过分)。
您可以随意使用和重用内存(和其他资源),但请确保在退出之前取消分配所有资源。
我想我应该添加这个小小的历史瑰宝,来自 Amiga 的 Rom 内核手册的屏幕截图,即官方平台文档。
评论
实际上,OSTEP在线教科书中有一个操作系统本科课程的部分,其中确切地讨论了您的问题。
相关部分是第 6 页内存 API 一章中的“忘记释放内存”,其中给出了以下解释:
在某些情况下,不调用 free() 似乎是合理的。为 例如,您的程序是短暂的,很快就会退出;在这种情况下, 当进程终止时,操作系统将清理其所有分配的页面,并 因此,本身不会发生内存泄漏。虽然这当然“有效” (见第 7 页的旁白),这可能是一个坏习惯,所以要小心 选择这样的策略
本摘录是在介绍虚拟内存概念的背景下进行的。基本上,在本书的这一点上,作者解释说,操作系统的目标之一是“虚拟化内存”,也就是说,让每个程序都相信它可以访问非常大的内存地址空间。
在后台,操作系统会将用户看到的“虚拟地址”转换为指向物理内存的实际地址。
但是,共享资源(如物理内存)需要操作系统跟踪哪些进程正在使用它。因此,如果进程终止,则在操作系统的功能和设计目标范围内,可以回收进程的内存,以便它可以重新分配内存并与其他进程共享内存。
编辑:摘录中提到的旁白复制如下。
旁白:为什么一旦进程退出,就不会泄漏内存
当你编写一个生存期较短的程序时,你可能会分配一些空间 用。程序运行并即将完成:是否存在 在退出之前需要打很多次电话吗?虽然看起来 错了,任何真正意义上的记忆都不会“丢失”。原因是 很简单:系统中实际上有两个级别的内存管理。 第一级内存管理由操作系统执行,操作系统 在进程运行时将内存分发给进程,并在运行时将其收回 进程退出(或以其他方式死亡)。第二级管理 在每个进程中,例如,在调用 和 时在堆中。即使你没有打电话(因此泄漏 内存),操作系统将回收 该过程(包括代码、堆栈和此处相关的页面, heap) 程序完成运行时。无论处于什么状态 在地址空间中的堆中,操作系统会收回所有这些页面 当进程死亡时,从而确保尽管 事实上,你没有释放它。
malloc()
free()
malloc()
free()
free()
因此,对于生存期较短的程序,泄漏内存通常不会导致任何 操作问题(尽管它可能被认为是糟糕的形式)。什么时候 编写长时间运行的服务器(如 Web 服务器或数据库管理 系统,永远不会退出),内存泄漏是一个更大的问题, 并最终导致应用程序用完时崩溃 记忆。当然,内存泄漏是一个更大的问题 一个特定的程序:操作系统本身。向我们展示一次 再说一遍:那些编写内核代码的人的工作是最艰巨的......
从内存 API 章节的第 7 页
操作系统: Three Easy Pieces
Remzi H. Arpaci-Dusseau 和 Andrea C. Arpaci-Dusseau Arpaci-Dusseau 书籍 2015年3月(版本 0.90)
如果程序在退出之前忘记释放几兆字节,操作系统将释放它们。但是,如果您的程序一次运行数周,并且程序内部的循环在每次迭代中忘记释放几个字节,那么您将产生巨大的内存泄漏,除非您定期重新启动它,否则将耗尽计算机中的所有可用内存=>如果程序用于一项非常大的任务,即使它最初不是设计的,即使是很小的内存泄漏也可能很糟糕一方面。
评论
这取决于您正在处理的项目的范围。在你的问题的上下文中,我的意思是只是你的问题,那么这并不重要。
为了进一步解释(可选),我从整个讨论中注意到的一些场景如下:
(1) - 如果你在一个嵌入式环境中工作,你不能依赖主操作系统为你回收内存,那么你应该释放它们,因为如果不注意,内存泄漏真的会使程序崩溃。
(2) - 如果你正在做一个个人项目,你不会向其他人透露它,那么你可以跳过它(假设你在主操作系统上使用它),或者为了“最佳实践”而包含它。
(3) - 如果你正在做一个项目并计划将其开源,那么你需要对你的受众进行更多的研究,并弄清楚释放内存是否是更好的选择。
(4) - 如果你有一个大型库,而你的受众只由主操作系统组成,那么你不需要释放它,因为他们的操作系统会帮助他们这样做。同时,通过不释放,您的库/程序可能有助于提高整体性能,因为程序不必关闭每个数据结构,从而延长了关闭时间(想象一下,在离开家之前关闭计算机的等待速度非常缓慢......
我可以继续指定要学习的课程,但这最终取决于你想通过你的计划实现什么。在某些情况下,释放内存被认为是好的做法,而在某些情况下则不然,因此它最终取决于您所处的具体情况以及在正确的时间提出正确的问题。祝你好运!
评论
free
malloc
正如其他人已经指出的那样,这取决于程序运行的操作系统环境,对于长时间运行的进程,释放内存并避免即使是非常缓慢的泄漏也始终很重要。但是,如果操作系统处理的东西,就像Unix可能永远所做的那样,那么你就不需要释放内存,也不需要关闭文件(当进程退出时,内核会关闭所有打开的文件描述符。 如果您的程序分配了大量内存,那么毫不犹豫地退出甚至可能是有益的。我发现当我退出Firefox时,它会花费几分钟!在许多进程中以 GB 内存分页。我想这是由于必须在 C++ 对象上调用析构函数。这其实很可怕。有些人可能会争辩说,这对于持续保存状态是必要的,但在我看来,长时间运行的交互式程序,如浏览器、编辑器和设计程序,仅举几例,应该确保任何状态信息、首选项、打开的窗口/页面、文档等经常被写入永久存储,以避免在崩溃时丢失工作。然后,当用户选择退出时,可以再次快速执行此状态保存,完成后,进程应立即退出。
为此过程分配的所有内存都将作系统标记为未使用,然后重用,因为内存分配是由用户空间函数完成的。
想象一下,OS是一个神,而记忆是创造过程的素材,神用一些素材创造了一个世界(或者说OS保留了一些内存,并在其中创造了一个进程)。无论这个世界的生物做了什么,不属于这个世界的材料都不会受到影响。这个世界过期后,神OS可以回收分配给这个世界的材料。
现代操作系统在释放用户空间内存方面可能有不同的细节,但这必须是操作系统的基本职责。
评论
free(a)
malloc()
sbrk()
malloc()
mmap()
free()