当你在程序终止前不释放malloc后,到底会发生什么?

我们都被教导必须释放每个已分配的指针。不过,我有点好奇不释放内存的真正代价。在一些明显的情况下,比如在循环内部或线程执行的一部分调用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之后释放是没有意义的,因为这些变量必须在程序执行期间始终存在,并且没有好方法(我可以看到)来实现静态分配的空间。分配了一堆内存,但只在进程结束时释放,这是糟糕的设计吗?如果是的话,有什么替代方案?

157940 次浏览

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

来源:分配和GC误区 (PostScript警告!)

分配神话4:非垃圾收集程序 应该总是释放所有内存 他们分配。< / p > 真相:省略 释放频繁执行 代码导致越来越多的泄漏。他们是 很少接受。而是那些 保留大部分已分配内存直到 程序退出通常执行得更好 没有任何干预的释放。 Malloc更容易实现,如果

在大多数情况下,释放内存 . . 操作系统无论如何都会回收它。免费的 会触碰并在死者身上翻页吗 对象;

后果:小心“泄漏 计算分配的检测器”。 有些“泄密”是好的!< / p >

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

第二个问题:你的设计还可以。如果你需要存储一些东西直到你的应用程序退出,那么使用动态内存分配是可以的。如果您事先不知道所需的大小,就不能使用静态分配的内存。

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

所以一般来说,这样做没有什么坏处,除了存储容量超过所需的运行时成本。当然,在您给出的示例中,您希望保留可能被使用的变量的内存,直到它被清除。

然而,一旦你不再需要内存,就立即释放内存,并在程序退出时释放你仍然拥有的内存,这被认为是一种很好的方式。它更像是一种了解您正在使用哪些内存的练习,并考虑是否仍然需要它。如果不进行跟踪,可能会出现内存泄漏。

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

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

如果进程是短期的,那么通常可以这样做,因为当进程完成时,所有分配的内存都会被操作系统回收,但我建议养成释放所有不再使用的内存的习惯。

退出时不释放内存是完全没问题的;Malloc()从称为“堆”的内存区域分配内存,当进程退出时释放整个堆。

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

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

编辑:说其他正在运行的程序被剥夺了该内存并不是100%准确。操作系统总是可以让他们使用它,代价是将程序交换到虚拟内存(</handwaving>)。关键是,如果您的程序释放了它不使用的内存,那么虚拟内存交换就不太可能是必要的。

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

如果您正在从头开始开发一个应用程序,那么您可以在何时调用free方面做出一些明智的选择。您的示例程序很好:它分配内存,也许您让它工作几秒钟,然后关闭,释放它所要求的所有资源。

但是,如果您正在编写其他任何东西——服务器/长时间运行的应用程序,或供其他人使用的库,则应该期望对malloc的所有内容调用free。

暂时忽略实用主义的一面,遵循更严格的方法,并强迫自己释放您malloc的所有内容要安全得多。如果您没有在编写代码时监视内存泄漏的习惯,那么很容易就会出现一些内存泄漏。换句话说,是的,你可以没有它;不过,请小心。

在那方面你完全正确。在小的程序中,变量必须存在,直到程序结束,释放内存并没有真正的好处。

事实上,我曾经参与过一个项目,在这个项目中,程序的每次执行都非常复杂,但时间相对较短,决定只是保持分配内存,而不是因为错误地释放内存而破坏项目的稳定。

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

我认为您的两个示例实际上只有一个:free()应该只在进程结束时出现,正如您指出的那样,这是无用的,因为进程正在终止。

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

=== 未来的打样代码重用呢?= = =

如果你写代码来释放对象,那么你就限制了代码的安全使用,只有当你可以依赖于内存被释放的进程被关闭…例如,小型一次性使用项目或“一次性”[1]项目)……你知道这个过程什么时候结束。

如果您编写的代码释放了所有动态分配的内存,那么您就可以在未来验证代码,并让其他人在更大的项目中使用它。


[1]关于“一次性”项目。在“一次性”项目中使用的代码有一种不被丢弃的方法。接下来,十年过去了,你的“一次性”代码仍在使用)。

我听说过一个故事,说有个人写了一些代码,只是为了让他的硬件工作得更好。他说“只是一个爱好,不会成为大而专业的”。多年后,很多人都在使用他的“爱好”代码。

这段代码通常可以正常工作,但是要考虑代码重用的问题。

你可能写了一些没有释放分配内存的代码片段,它以这样一种方式运行,然后自动回收内存。似乎还好吧。

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

代码重用在企业中很常见。通常公司拥有其员工生产的所有代码,每个部门都可以重用公司拥有的任何代码。因此,通过编写这种“看起来很无辜”的代码,您可能会给其他人带来潜在的麻烦。这可能会让你被炒鱿鱼。

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

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

关于你的第二个问题,如果我的程序输入a=5,我会为a分配空间,或者在后续的a="foo"上重新分配相同的空间。这笔款项将继续分配至:

  1. 用户输入'unset a'
  2. 我的清理功能被输入,要么服务一个信号,要么用户输入“退出”

我想不出任何现代操作系统在进程退出后不回收内存。free()很便宜,为什么不清理一下呢?正如其他人所说,像valgrind这样的工具对于发现您确实需要担心的泄漏非常有用。即使你示例中的块被标记为“仍然可达”,当你试图确保没有泄漏时,它只是输出中的额外噪音。

另一个误区是“如果它在main()中,我不需要释放它”,这是不正确的。考虑以下几点:

char *t;


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

如果这发生在fork / daemonizing(理论上永远运行)之前,那么您的程序已经泄漏了255次大小不确定的t。

一个好的,编写良好的程序应该总是自我清理。释放所有内存,刷新所有文件,关闭所有描述符,解除所有临时文件的链接等等。应该在正常终止或接收到各种致命信号时执行此清理功能,除非您想要保留一些文件以便检测崩溃并恢复。

真的,当你去做其他事情的时候,要善待那些不得不维护你的东西的可怜人。递给他们“valgrind clean”:)

你说得对,不会造成伤害,直接退出会更快

原因有很多:

  • 所有桌面和服务器环境都会在exit()时释放整个内存空间。他们不知道程序内部的数据结构,比如堆。

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

  • 更重要的是,在exit()之前执行是浪费时间。在退出时,仅释放内存页和交换空间。相反,一系列free()调用将消耗CPU时间,并可能导致磁盘分页操作、缓存丢失和缓存清除。

关于未来代码重用的可能性为毫无意义的操作的确定辩护:这是一个考虑,但可以说不是敏捷的方式。# EYZ3

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

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

但是如果我在一个没有操作系统的环境中呢? 哪里什么都没有?< / p > 想象一下,现在您正在使用线程样式的中断并分配内存。 在C标准ISO/IEC:9899中,内存的生命周期表示为:

7.20.3内存管理功能

1连续调用分配给callloc的存储的顺序和相邻性, Malloc, realloc函数未指定。指针返回如果分配 成功是适当对齐,以便它可以被分配给指向任何类型对象的指针 然后用于在分配的空间中访问这样一个对象或这样一个对象的数组 (直到空间被显式地释放)。已分配对象的生命周期会延长 从分配到回收[…]

所以不能说环境在为你做释放的工作。 否则它将被添加到最后一句:"或直到程序终止。"

换句话说 不释放内存不仅仅是不好的做法。它生成不可移植且不符合C的代码。 至少可以被视为“正确的”,如果有以下情况:[…]

.],由environment'支持 但是在你根本没有操作系统的情况下,没有人会为你做这项工作 (我知道通常在嵌入式系统中不会分配和重新分配内存, 但在某些情况下,你可能想这样做。)

所以在一般的纯C中(OP被标记), 这只是产生错误的和不可移植的代码

真正的结果是什么?

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

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

但是您依赖的是一个不属于该语言的安全特性,您不应该依赖它。您的代码可能运行在这样一个系统上:行为导致"hard"内存泄漏,下一个次。

你的代码最终可能会在内核模式下运行,或者在老式/嵌入式操作系统上运行,这些操作系统不采用内存保护作为权衡。(mmu占用芯片空间,内存保护成本额外的CPU周期,并且要求程序员自己清理并不过分)。

您可以以任何您喜欢的方式使用和重用内存(和其他资源),但请确保在退出之前释放了所有资源。

在操作系统本科课程的在线教材OSTEP中,有一个章节恰好讨论了你的问题。

相关的部分是在第6页的内存API章节中“忘记释放内存”,给出了以下解释:

在某些情况下,不调用free()似乎是合理的。为 例如,你的程序是短命的,很快就会退出;, 当进程死亡时,操作系统将清理它分配的所有页面 因此,内存泄漏本身不会发生。虽然这当然“有效” (见第7页的旁白),这可能是一个坏习惯,所以要警惕 选择这样的策略

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

在幕后,操作系统会将用户看到的“虚拟地址”转换为指向物理内存的实际地址。

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


以下是节选中提到的旁白。

旁白:# EYZ0

当你编写一个短寿命的程序时,你可能会分配一些空间 使用# EYZ0。程序运行并即将完成:is there 需要在退出之前调用free()一堆次?虽然看起来 错不去,没有记忆会“失去”任何真正意义上的东西。原因是 很简单:系统中实际上有两个级别的内存管理。 第一级内存管理是由操作系统执行的 进程运行时将内存分配给进程,运行时将内存收回 进程退出(或者死亡)。第二层次的管理 是在每个进程中,例如在堆中调用时 malloc()free()。即使您未能调用free()(从而泄漏 内存在堆中),操作系统将回收的所有内存 这个过程(包括代码、堆栈和这里相关的页面) 堆)当程序完成运行时。无论什么状态 你的堆在你的地址空间,操作系统收回所有这些页 当进程死亡时,从而确保不丢失内存 你没有释放它的事实。

因此,对于短寿命的程序,内存泄漏通常不会引起任何问题 操作问题(尽管它可能被认为是糟糕的形式)。当 编写一个长期运行的服务器(如web服务器或数据库管理) 系统,永远不会退出),内存泄漏是一个更大的问题, 当应用程序耗尽时,将最终导致崩溃 内存。当然,内存泄漏在内部是一个更大的问题 一个特殊的程序:操作系统本身。给我们展示一次 再次强调:那些编写内核代码的人的工作是最艰难的

从第7页内存API章节

< p > # EYZ0 < br > Remzi H. Arpaci-Dusseau和Andrea C. Arpaci-Dusseau Arpaci-Dusseau书 2015年3月(版本0.90)

如果一个程序在退出前忘记释放几兆字节,操作系统将释放它们。但是如果你的程序一次运行几个星期,并且程序内部的循环忘记在每次迭代中释放几个字节,你将会有一个巨大的内存泄漏,它将耗尽你计算机中所有可用的内存,除非你定期重新启动它=>如果程序用于一个非常大的任务,即使很小的内存泄漏也可能是不好的,即使它最初并不是为它设计的。

这取决于你正在做的项目的范围。在你的问题的背景下,我是说仅仅是你的问题,那就不重要了。

为了进一步解释(可选),我从整个讨论中注意到的一些场景如下:

(1) -如果你在一个嵌入式环境中工作,你不能依靠主操作系统来回收内存,那么你应该释放它们,因为如果不注意的话,内存泄漏真的会使程序崩溃。

(2) -如果你正在做一个个人项目,你不会向任何人透露它,那么你可以跳过它(假设你在主操作系统上使用它),或者把它作为“最佳实践”。的缘故。

(3) -如果你正在开发一个项目,并计划让它开源,那么你需要对你的受众做更多的研究,并弄清楚释放内存是否是更好的选择。

(4)如果你有一个很大的库,而你的用户只有主操作系统,那么你就不需要释放它,因为他们的操作系统会帮助他们这么做。同时,通过不释放,你的库/程序可能有助于提高整体性能,因为程序不需要关闭每个数据结构,延长关机时间(想象一下,在离开家之前,你要非常缓慢地等待关机……)

我可以继续指定要采取哪一种方法,但这最终取决于您想用程序实现什么。释放内存在某些情况下被认为是很好的实践,但在某些情况下则不是,因此它最终取决于您所处的具体情况,以及在正确的时间提出正确的问题。好运!

正如其他人已经注意到的,这取决于程序运行的操作系统环境,对于长时间运行的进程,释放内存和避免非常缓慢的泄漏总是很重要的。但是如果操作系统处理一些事情,比如Unix可能永远都在做,那么你就不需要释放内存,也不需要关闭文件(当进程退出时,内核会关闭所有打开的文件描述符)。 如果你的程序分配了大量的内存,它甚至可能是有益的退出没有“犹豫”。我发现当我退出Firefox时,它花了好几分钟!在许多进程中分页千兆字节的内存。我猜这是因为必须在c++对象上调用析构函数。这真的很糟糕。有些人可能会认为,这对于持续保存状态是必要的,但在我看来,长期运行的交互式程序,如浏览器、编辑器和设计程序,仅举几个例子,应该确保任何状态信息、首选项、打开的窗口/页面、文档等都经常被写入永久存储,以避免崩溃时的工作损失。然后,当用户选择退出时,可以快速地再次执行这种状态保存,当完成时,进程应该立即退出

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

想象OS是一个上帝,而记忆是创造一个过程世界的材料,上帝用一些材料创造了一个世界(或者说OS保留了一些内存并在其中创造了一个过程)。无论这个世界上的生物做了什么,不属于这个世界的物质都不会受到影响。在这个世界过期后,OS神,可以回收分配给这个世界的材料。

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