如果我需要在程序的整个生命周期中使用一块内存,是否真的有必要在程序终止之前释放它?


67

在许多书籍和教程中,我都听说过内存管理的做法很受压力,并感到如果使用完内存后不释放内存,将会发生一些神秘而可怕的事情。

我无法代表其他系统(尽管对我而言,可以假设它们采用了类似的做法是合理的),但是至少在Windows上,内核可以保证清除由以下系统使用的大部分资源(少数情况除外)程序终止后的程序。其中包括堆内存等。

我了解为什么您要在使用完文件后关闭文件以使文件可供用户使用,或者为什么要断开连接到服务器的套接字以节省带宽,但是这样做似乎很愚蠢。必须微管理程序使用的所有内存。

现在,我同意这个问题是广泛的,因为您应该如何处理内存取决于您需要多少内存以及何时需要,因此我将把这个问题的范围缩小到这个范围:如果我需要使用在程序整个生命周期中的内存,在程序终止之前是否真的有必要释放它?

编辑:建议作为重复的问题是特定于Unix家族的操作系统。它的最高答案甚至指定了特定于Linux的工具(例如Valgrind)。该问题旨在涵盖大多数“常规”非嵌入式操作系统,以及为什么在程序的整个生命周期内释放所需的内存是一个好习惯,或不是一个好习惯。



19
您已经将此语言标记为不可知的,但是在许多语言(例如Java)中,根本没有必要手动释放内存。在最后一次引用对象超出范围后的某个时间它会自动发生
Richard Tingle

3
当然,您可以编写C ++而无需任何删除操作,这对于内存来说是很好的
Nikko 2015年

5
@RichardTingle虽然我想不出C和C ++之外的任何其他语言,但与语言无关的标记旨在覆盖所有内置了很多垃圾回收实用程序的语言。但是,如今这种情况很少见。您可能会争辩说现在可以用C ++实现这样的系统,但是最终您仍然可以选择不删除内存。
CaptainObvious15年

4
您写道:“基本上可以保证内核在程序终止后清除所有资源”。通常这是错误的,因为并非所有内容都是内存,句柄或内核对象。但是对于记忆确实如此,您必须立即限制问题。
埃里克·塔

Answers:


108

如果我需要在程序的整个生命周期中使用一块内存,是否真的有必要在程序终止之前释放它?

它不是强制性的,但它可以带来好处(以及一些缺点)。

如果程序在执行期间分配一次内存,否则在进程结束之前永远不会释放内存,因此明智的做法是不手动释放内存并依靠OS。在我所知道的每个现代OS上,这都是安全的,在过程结束时,所有分配的内存都可靠地返回给系统。
在某些情况下,不显式清除分配的内存甚至可能比进行清除更快。

但是,通过在执行结束时显式释放所有内存,

  • 在调试/测试过程中,内存泄漏检测工具不会向您显示“误报”
  • 将使用内存以及分配和释放的代码移到一个单独的组件中,然后在不同的上下文中使用它可能更容易,因为在这种情况下,内存的使用时间需要由组件的用户来控制

程序的寿命可以改变。也许您的程序是今天的小型命令行实用程序,通常寿命不到10分钟,并且每10秒以部分kb的形式分配内存-因此,无需在程序结束前释放任何已分配的内存。后来程序被更改,并且作为服务器进程的一部分得到了扩展的使用,并且具有几个星期的生命期-因此,不再释放介于两者之间的未使用内存已不再是一种选择,否则您的程序将逐渐消耗掉所有可用的服务器内存。这意味着您将必须检查整个程序并在之后添加取消分配的代码。如果幸运的话,这是一件容易的事,否则,很难错过一个地方。而在这种情况下,您希望您添加了“免费”

更一般而言,在许多程序员中,编写分配代码和相关的分配代码始终成对算作“好习惯”:通过始终这样做,可以降低在必须释放内存的情况下忘记释放代码的可能性。


12
关于养成良好的编程习惯的要点。
劳伦斯

4
通常,我完全同意此建议。保持良好的记忆习惯非常重要。但是,我认为也有例外。例如,如果您分配的是一个疯狂的图形,将花费几秒钟的时间来遍历并“适当地”释放它,那么您将通过结束程序并让OS操纵它来改善用户体验。
GrandOpener 2015年

1
它在每个现代的“普通”(非嵌入式,带有内存保护)操作系统上都是安全的,如果不是,那将是一个严重的错误。如果无特权的进程可能永久丢失操作系统不会回收的内存,则多次运行该进程可能会使系统内存不足。操作系统的长期运行状况不能取决于非特权程序中是否缺少错误。(当然,这是在忽略诸如Unix共享内存段之类的东西,这些段最多只能由交换空间来支持。)
Peter Cordes

7
@GrandOpener在这种情况下,您可能更喜欢为此树使用某种基于区域的分配器,以便您可以按常规方式分配它,并在时间到时立即一次分配整个区域,而不是遍历并释放它它一点一点。那仍然是“正确的”。
托马斯

3
另外,如果在程序的整个生命周期中确实需要内存,那么在堆栈上创建结构可能是明智的选择,例如在中main
Kyle Strand

11

在程序运行结束时释放内存只是浪费CPU时间。这就像在将房子从轨道上挪开之前整理一下房子。

但是有时运行时间短的程序可能会变成运行时间更长的程序的一部分。然后释放东西变得必要。如果至少在某种程度上没有考虑到这一点,则可能涉及大量的返工。

一种巧妙的解决方案是“ talloc”,它可以让您分配大量的内存分配,然后一次调用就将其全部丢弃。


1
假设您知道自己的操作系统没有泄漏。
WGroleau 2015年

5
“浪费CPU时间”虽然时间非常少。free通常比malloc
保罗·德雷珀

@PaulDraper Aye,浪费时间将所有内容交换回去的意义更大。
Deduplicator 2016年

5

您可以使用带有垃圾回收的语言(例如Scheme,Ocaml,Haskell,Common Lisp,甚至Java,Scala,Clojure)。

(在大多数GC-ED语言,有没有办法明确手动释放内存有时候,一些值可能!敲定,如GC和运行时系统,当该值不能到达将关闭文件句柄值,但这不是肯定,不可靠,而您应该改为明确关闭文件句柄,因为决不能保证最终确定)

对于以C(甚至C ++)编码的程序,您也可以使用Boehm的保守垃圾收集器。然后,您将用替换所有内容mallocGC_malloc 而不必担心free-ing任何指针。当然,您需要了解使用Boehm GC的利弊。另请阅读GC手册

内存管理是程序的全局属性。从某种意义上讲,它(以及某些给定数据的有效性)是非组成性和非模块化的,因为它具有整个程序的属性。

最后,正如其他人指出的那样,显式地-为free堆分配的C内存区域-是好的做法。对于不分配大量内存的玩具程序,您甚至可以决定根本不使用free内存(由于进程结束后,其资源(包括其虚拟地址空间)将被操作系统释放)。

如果我需要在程序的整个生命周期中使用一块内存,是否真的有必要在程序终止之前释放它?

不,您不需要这样做。而且,许多现实世界中的程序都不会费心释放整个生命周期所需的某些内存(特别是GCC编译器不会释放某些内存)。但是,当您这样做时(例如,您不必打扰- free某些特定的C动态分配的数据),您最好对该事实进行注释,以减轻将来的程序员在同一项目上的工作。我倾向于建议未释放的内存数量保持有限,并且通常相对于总使用的堆内存而言较小。

请注意,系统free通常不向OS释放内存(例如,通过在POSIX系统上调用munmap(2)),但通常会将内存区域标记为将来可重用malloc。特别是,虚拟地址空间(例如,/proc/self/maps在Linux上可以看到,请参见proc(5) ...。)在此之后可能不会缩小free(因此,实用程序会为进程报告pstop使用相同数量的已使用内存)。


3
“有时,某些值可能会最终确定,例如,当该值不可访问时,GC和运行时系统会关闭文件句柄值”。您可能要强调的是,在大多数GCed语言中,不能保证将回收任何内存,也不能保证任何终结以往运行,甚至在退出:stackoverflow.com/questions/7880569/...
Deduplicator

我个人更喜欢这个答案,因为它鼓励我们使用GC(即自动化程度更高的方法)。
Ta Thanh Dinh 2015年

3
@tathanhdinh更加自动化,尽管专门用于内存。完全手册,适用于所有其他资源。GC通过交换确定性和内存来工作,以方便地处理内存。不,终结器没有太大帮助,并且有自己的问题。
Deduplicator

3

这是没有必要的,因为如果失败,您将不会失败,无法正确执行程序。但是,如果有机会,您可能会选择一些理由。

我遇到的最有力的案例之一是(某人一遍又一遍地)有人编写了一小段在其可执行文件中运行的仿真代码。他们说:“我们希望将此代码集成到模拟中。” 然后我问他们如何计划在两次蒙特卡洛比赛之间重新初始化,他们茫然地看着我。“您是什么意思重新初始化?您只是使用新设置运行程序?”

有时,一个干净的清理使得很多更容易使用你的软件。在许多示例中,您假定您不需要清理某些东西,并且假设如何处理这些假设及其周围的数据。当您转移到这些假定无效的新环境时,整个算法可能不再起作用。

有关如何获得奇怪结果的示例,请查看托管语言如何在流程结束时处理终结处理,或C#如何处理应用程序域的程序停止。他们之所以纠缠不清,是因为在这些极端情况下,各种假设会落空。


1

忽略仍然无法手动释放内存的语言...

您现在认为的“程序”可能在某个时候变成只是更大程序一部分的函数或方法。然后该函数可能会被多次调用。然后,您应该“手动释放”的内存将导致内存泄漏。那当然是一个判断电话。


2
这似乎只是重复在几个小时前发布的最佳答案中提出的观点(并作了更好的解释):“将使用内存以及分配和释放的代码移动到单独的组件中,然后再使用它可能会容易得多。在不同的情况下,内存的使用时间需要由组件的用户控制...”
gnat 2015年

1

因为很可能在某些时候您想要更改程序,或者将其与其他程序集成,依次或并行运行多个实例。然后,有必要手动释放此内存-但是您将不会再记住这种情况了,这将花费您更多的时间来重新理解您的程序。

在您对它们的了解仍然新鲜的同时做一些事情。

这是一笔很小的投资,将来可能会产生丰厚的回报。


2
在先前的11个答案中提出和解释的观点看来,这似乎并没有增加任何实质性内容。特别是,关于将来可能更改程序的要点已经提出了三到四次
gnat 2015年

1
@gnat以一种令人费解和困惑的方式-是的。明确声明-不。让我们专注于回答质量,而不是快速性。
Agent_L

1
在质量方面,最好的答案似乎更好地解释了这一点
gnat 2015年

1

不,没有必要,但这是一个好主意。

您说您感到“如果在使用完内存后不释放内存,将会发生神秘而可怕的事情。”

用技术术语来说,这样做的唯一结果是,程序将继续消耗更多的内存,直到达到硬限制(例如,虚拟地址空间已用完)或性能变得不可接受为止。如果程序将要退出,则这无关紧要,因为该过程实际上不再存在。“神秘而可怕的事情”纯粹是关于开发商的精神状态。寻找内存泄漏的源头绝对是一场噩梦(这是一种轻描淡写的说法),并且编写无泄漏的代码需要大量的技巧和纪律。推荐的开发这种技能和纪律的方法是,一旦程序不再需要,就总是释放不再需要的内存。

当然,这样做还有一个好处,就是您的代码可以重复使用和修改,就像其他人所说的那样。

但是,至少有一种情况最好不要在程序终止之前释放内存。

请考虑以下情况:您已经进行了数百万个小型分配,并且大部分已交换到磁盘上。当您开始释放所有内容时,您需要将大部分内存换回RAM,以便可以访问簿记信息,而只是立即丢弃数据。这可能会使程序需要几分钟才能退出!更不用说在此期间,磁盘和物理内存承受了很大压力。如果开始时物理内存不足(也许由于另一个程序占用了很多内存而正在关闭程序),那么当需要从中释放多个对象时,可能需要多次换入和换出单个页面。同一页,但不会连续释放。

如果相反,程序只是中止运行,则OS会简单地丢弃已交换到磁盘的所有内存,这几乎是瞬时的,因为它根本不需要任何磁盘访问。

重要的是要注意,在OO语言中,调用对象的析构函数也将迫使内存被交换。如果必须执行此操作,则最好释放内存。


1
您能否引用一些东西来支持需要调出分页内存才能取消分配的想法?那显然是低效的,肯定是现代的操作系统比这更聪明!

1
@Jon of All Trades,现代的操作系统很聪明,但是具有讽刺意味的是,大多数现代语言都不是。当您释放(某物)时,编译器将“东西”放入堆栈中,然后调用free()。如果将其交换出栈,则将其放回堆栈需要将其交换回RAM中。
Jeffiekins 2015年

@JonofAllTrades一个自由列表会产生这种效果,尽管这是一个相当过时的malloc实现。但是操作系统无法阻止您使用这种实现。

0

手动清理的主要原因是:您不太可能将真正的内存泄漏误认为是将在退出时清理的内存块;有时,只有当您将整个数据结构移至分配器时,您才会在分配器中捕获错误。取消分配它,如果您进行重构,则头疼要小得多,以便在程序退出之前确实需要重新分配某些对象。

不进行手动清理的主要原因是:性能,性能,性能以及不必要的清理代码中是否可能存在两次免费两次或多次使用的错误,这些错误可能会导致崩溃或安全​​性。利用。

如果您关心性能,则总是希望通过分析来发现自己一直在浪费时间,而不是猜测。可能的折衷方法是将可选的清除代码包装在条件块中,在调试时将其保留,以便从编写和调试代码中受益,然后,当且仅当您凭经验确定它太多时,开销,告诉编译器在最终可执行文件中跳过它。顾名思义,关闭程序几乎没有任何困难。

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.