内存泄漏能走多远?


118

我已经多次遇到内存泄漏。通常,当我- malloc希望没有明天,或者FILE *像脏衣服一样晃来晃去时。我通常认为(拼命:希望)至少在程序终止时才清理所有内存。在程序终止或崩溃时,是否存在无法收集泄漏内存的情况?

如果答案因语言而异,那么让我们关注C(++)。

请注意,短语“好像没有明天”和“晃来晃去……就像脏衣服”一样是双曲线的用法。不安全* malloc可能伤害您所爱的人。另外,请注意不要弄脏衣物。


3
如果您正在使用“现代”操作系统(例如Linux或Windows)运行,则在程序终止时,操作系统本身将解析所有未释放的内存。
奥利弗·查尔斯沃思

60
与其假装没有明天,不如假装有明天并跟踪您的记忆!
William Pursell 2013年

8
@WilliamPursell啊,所以你是说一个人应该calloc没有明天。优秀的。
DilithiumMatrix

8
“如果答案因语言而异,请关注c(++)。” CC ++语言不同!
Johnsyweb

11
@zhermes:有关C和C ++是不同语言的评论隐藏的比您想象的要多...在C ++中,您宁愿发现自己利用具有自动存储持续时间的对象,遵循RAII习惯用法...让这些对象负责内存处理为您管理。
LihO

Answers:


111

否。操作系统在退出时会释放进程所拥有的所有资源。

这适用于操作系统维护的所有资源:内存,打开的文件,网络连接,窗口句柄...

也就是说,如果程序在没有操作系统的嵌入式系统上运行,或者在非常简单或错误的操作系统上运行,则在重新启动之前,内存可能无法使用。但是,如果您处在这种情况下,您可能不会问这个问题。

操作系统可能需要很长时间才能释放某些资源。例如,即使程序已正确关闭,网络服务器用来接受连接的TCP端口也可能需要几分钟才能释放。联网程序还可以保存远程资源,例如数据库对象。当网络连接丢失时,远程系统应释放这些资源,但是它可能比本地操作系统花费更长的时间。


5
RTOS中的常见范例是单进程,多线程模型,并且“任务”之间没有内存保护。通常只有一堆。当然,这就是VxWorks过去的工作方式-可能仍然如此。
marko

29
请注意,并非所有资源都可以由操作系统释放。网络连接,数据库事务等未明确关闭它们可能会导致某些不良结果。不关闭网络连接可能导致服务器认为您在不确定的时间内仍然处于活动状态,而对于限制活动连接数的服务器,可能会意外导致拒绝服务。不关闭数据库事务可能会导致您丢失未提交的数据。
Lie Ryan

1
@Marko:vxWorks的最新版本现在支持支持内存保护的RTP(实时进程)。
Xavier T.

20
“操作系统在进程退出时会释放它们所拥有的所有资源。” 并非完全正确。例如,在(至少)Linux上,在进程退出时不会清除SysV信号量和其他IPC对象。这就是为什么要ipcrm进行手动清理的原因,linux.die.net/man/8/ipcrm
sleske

7
另外,如果对象具有要维护的临时文件,则此后显然不会清除。
Mooing Duck 2013年

47

C标准未指定malloc在程序终止时释放由分配的内存。这是由操作系统完成的,并不是所有的OS(通常在嵌入式世界中)都会在程序终止时释放内存。


20
那或多或少是因为C标准谈论的是C程序,而不是运行C的操作系统……
vonbrand

5
@vonbrand C标准本来可以说一段,当main返回时,malloc释放由分配的所有内存。例如,它说所有打开的文件在程序终止前都已关闭。对于分配了my的内存malloc,只是未指定。当然,现在我关于操作系统的一句话描述的是标准所规定的通常没有做的事情,因为它没有对此做任何规定。
哇,2013年

让我纠正我的意见:该标准谈论C,而不是关于如何启动和停止该程序。您可以很好地编写无需操作系统即可运行的C程序。在这种情况下,没有人会进行清理。该标准非常故意不指定任何东西,除非需要,以免不必要地限制使用。
vonbrand

2
@ouah:“ main返回时...”。这是一个假设。我们必须考虑“ 如果主要回报...”。std::atexit还考虑通过终止程序std::exit,然后还有std::abort和(特定于C ++)std::terminate
MSalters

@ouah:如果已包括在内, atexit将无法使用。:-)
R .. GitHub停止帮助ICE 2013年

28

由于所有答案都涵盖了现代OS的大部分问题,但是从历史上看,如果您曾经在DOS世界中编程过,那么值得一提的是。终止驻留程序(TSR)通常会将控制权返回给系统,但驻留在内存中,可以通过软件/硬件中断来恢复。在这些操作系统上工作时,通常会看到诸如“内存不足!尝试卸载某些TSR”之类的消息

因此,从技术上讲,该程序将终止,但由于它仍驻留在内存中,因此除非您卸载该程序,否则不会释放任何内存泄漏。

因此,您可以考虑这是另一种情况,除了操作系统由于存在故障或嵌入式OS旨在回收内存而没有回收内存。

我还记得一个例子。 客户信息控制系统(CICS)是主要在IBM大型机上运行的事务服务器,它是伪会话。在执行时,它处理用户输入的数据,为用户生成另一组数据,传输到用户终端节点并终止。激活注意键后,它会再次恢复以处理另一组数据。因为它的行为方式,从技术上来说,再次是,操作系统不会从终止的CICS程序中回收内存,除非您回收CICS事务服务器。


真的很有趣,谢谢您的历史记录!您是否知道该范式是否是由于不必要的释放内存而导致的计算量过大?还是从未想到过替代方案?
DilithiumMatrix

1
@zhermes:这在计算上是不可能的,因为DOS根本不跟踪TSR的内存分配。顾名思义,目标是保持常驻。如果您希望TSR释放部分而非全部内存,则由您决定释放什么。
MSalters

2
@zhermes:DOS(就像CP / M的前身一样)并不是现代意义上的操作系统。实际上,这只是一组I / O实用程序,可以通过标准方式与命令处理器捆绑在一起进行调用,该命令处理器可使您一次运行一个程序。没有进程的概念,并且内存既不是虚拟的也不是受保护的。TSR是一个有用的黑客工具,它可以告诉系统它们占用了64K的空间,并将自己挂接到中断中,因此它们会被调用。
Blrfl 2013年

8

就像其他人所说的那样,大多数操作系统将在进程终止时回收分配的内存(可能还会回收其他资源,例如网络套接字,文件句柄等)。

话虽如此,在处理new / delete(而不是raw malloc / free)时,内存可能并不是您唯一需要担心的事情。可以回收用new分配的内存,但是不会在对象的析构函数中完成任何事情。也许某个类的析构函数在销毁时将标记值写入文件中。如果该过程刚刚终止,则可能会刷新文件句柄并回收内存,但不会写入哨兵值。

故事的寓意,总是要自己清理。不要让事情悬而未决。在您执行完操作后,请不要依靠操作系统进行清理。自己清理一下。


“不要依赖于您之后清理操作系统。自己清理一下。” 这通常意味着...使用复杂的多线程应用程序“非常非常困难”。丢失所有对资源的引用的实际泄漏是严重的。允许操作系统清理而不是显式释放引用并不总是件坏事,并且通常是唯一合理的选择。
马丁·詹姆斯

1
在C ++中,析构函数在程序终止被调用(除非出现了一些不太聪明的kill -9爱好者……)
vonbrand

@vonbrand是的,但是如果我们谈论动态对象的泄漏,则不会发生这些析构函数。超出范围的对象是原始指针,其析构函数是无操作。(当然,请参阅RAII对象以缓解此问题...)
Andre Kostur

1
RAII的问题在于,它坚持要在进程退出时取消分配对象,而摆脱对象实际上并不重要。您需要注意的数据库连接,但是最好由OS清理常规内存(它做得更好)。问题表现为一个程序,一旦分页的内存增加,该程序将花费绝对的时间才能退出。解决问题也
Donal Fellows

@vonbrand:并不是那么简单。std::exit会调用dtor,std::abort不会,未捕获的异常可能会。
MSalters 2013年

7

这更可能取决于操作系统而不是语言。最终,任何语言的任何程序都将从操作系统获取其内存。

我从未听说过在程序退出/崩溃时不会回收内存的操作系统。因此,如果您的程序在需要分配的内存上具有上限,那么仅分配而不永不释放是完全合理的。


如果是简单的OS,您能搞乱内核的内存画面吗?。就像那些甚至没有多任务处理的操作系统一样。
ulidtko

@ulidtko,这会使事情搞砸。如果我的程序不时要求说1GiB,并且在整个过程中都抓取到它,那就是在不使用时拒绝向其他人使用这些资源。这可能今天很重要。但是环境发生根本变化。保证。
vonbrand

@vonbrand稀有使用1GiB通常不是问题(只要您有足够的物理内存),因为现代操作系统可以分页当前不活动的位。问题是当你有更多的虚拟内存活跃使用比你有在举办它的物理内存。
Donal Fellows 2013年

5

如果将程序转换为动态组件(“插件”)并加载到另一个程序的地址空间中,则即使在具有整洁内存管理的操作系统上,它也会很麻烦。我们甚至不必考虑将代码移植到功能较弱的系统上。

另一方面,释放所有内存可以影响程序清理的性能。

我正在开发的一个程序,某个测试用例需要30秒或更长时间才能退出该程序,因为该程序要遍历所有动态内存的图形并逐个释放。

合理的解决方案是在其中具有功能并用测试用例覆盖它,但在生产代码中将其关闭,以使应用程序快速退出。


5

所有带有标题的操作系统都将清除终止后您的过程造成的混乱。但是总是有无法预料的事件,如果以某种方式被拒绝访问并且某个可怜的程序员没有预见到这种可能性,那么它以后不会再尝试怎么办?如果内存泄漏是关键任务,则总是更安全地清理自己即可;否则,如果付出高昂的代价,IMO真的不值得付出这种努力。

编辑:如果确实存在内存泄漏,那么您确实需要清理内存泄漏,例如循环。我所说的内存泄漏是在整个程序过程中不断累积的,如果您有任何其他类型的泄漏,迟早很可能是一个严重的问题。

从技术上讲,如果泄漏是内存“复杂性” O(1),则在大多数情况下都可以,O(logn)已经令人不快(在某些情况下是致命的),并且O(N)+无法忍受。


3

兼容POSIX的系统上的共享内存将一直保留,直到调用shm_unlink或重新引导系统为止。


2

如果您具有进程间通信,则可能导致其他进程永远无法完成并消耗资源,具体取决于协议。

举个例子,当我在打印机工作中终止JVM时,我曾经尝试用Java打印到PDF打印机,PDF假脱机过程仍然处于活动状态,我必须在任务管理器中将其杀死,然后才能重试打印。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.