当你在malloc之后没有自由时真的会发生什么?

这已经困扰了我很多年了。

我们都在学校教书(至less,我是),你必须释放每一个分配的指针。 不过,对于没有释放内存的真正代价,我有些好奇。 在一些明显的情况下,例如malloc在循环或线程执行的一部分中被调用时,释放非常重要,所以没有内存泄漏。 但是请考虑以下两个示例:

首先,如果我有这样的代码:

 int main() { char *a = malloc(1024); /* Do some arbitrary stuff with 'a' (no alloc functions) */ return 0; } 

这里真正的结果是什么? 我的想法是,这个过程死了,然后堆空间已经消失了,所以没有任何伤害,错过了free的呼吁(但是,我确实认识到无论如何,closures,可维护性和良好的做法的重要性)。 我对这个想法是否正确?

其次,让我们说我有一个程序,有点像一个壳。 用户可以声明像aaa = 123这样的variables,并将这些variables存储在某些dynamic数据结构中供以后使用。 很显然,你会使用一些解决scheme来调用一些* alloc函数(hashmap,链表,类似的东西)。 对于这种types的程序,在调用malloc之后永远不会释放,因为这些variables必须在程序执行期间一直存在,并且没有什么好的方法(我可以看到)使用静态分配的空间来实现这一点。 有一堆分配的内存,但只作为过程结束的一部分被释放,这是不好的devise吗? 如果是的话,还有什么select?

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

所以通常情况下,除了比您需要更多的存储空间的运行时间成本之外,没有什么坏处。 当然,在你给的例子中,你想保留一个variables的内存,直到它被清除。

然而,一旦你不再需要它,释放内存就被认为是一种好的方式,并且释放你在程序退出时仍然存在的任何东西。 这更多的是了解你正在使用的记忆,并考虑你是否还需要记忆。 如果你不跟踪,你可能有内存泄漏。

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

是的你是对的,你的榜样没有任何伤害(至less不是在大多数现代操作系统上)。 一旦进程退出,所有由进程分配的内存将被操作系统恢复。

来源: 分配和GC神话 (PostScript警报!)

分配神话4:非垃圾收集程序应该总是释放所有分配的内存。

真相:在经常执行的代码中省略释放导致越来越多的泄漏。 他们很less接受。 但是直到程序退出时保留大部分分配内存的程序通常执行得更好,没有任何干预的重新分配。 如果没有免费的话,Malloc更容易实现。

在大多数情况下, 在程序退出之前释放内存是毫无意义的。 操作系统将无论如何回收它。 免费将触摸和页面中的死物; 操作系统不会。

结果:小心使用计数分配的“泄漏检测器”。 有些“泄漏”是好的!

这就是说,你应该尽量避免所有的内存泄漏!

第二个问题:你的devise是好的。 如果你需要存储的东西,直到你的应用程序退出,那么可以做到这一点与dynamic内存分配。 如果您不了解所需的大小,则不能使用静态分配的内存。

=== 未来的校对代码重用如何? ===

如果你写代码来释放这些对象,那么当你可以依赖被closures的进程释放内存的时候,你将代码限制为只能安全使用。

如果你写的代码是free()所有你dynamic分配的内存的话,那么你将来可以validation代码并让其他人在更大的项目中使用它。

你是对的,没有伤害,只是退出更快

有这么多的原因:

  • 所有桌面和服务器环境只需释放exit()上的整个内存空间即可。 他们不知道程序内部的数据结构,如堆。

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

  • 更重要的是,在exit()之前完成是浪费时间。 在退出时,内存页面和交换空间只是简单的释放。 相比之下,一系列free()调用将会消耗CPU时间,并可能导致磁盘分页操作,caching未命中和caching逐出。

关于未来代码重用的可能性,certificate无意义操作的确定性 :这是一个考虑,但它可以说是不是敏捷的方式。 YAGNI!

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

所以,如果发生这种情况,我现在有一个泄漏。

关于你的第二个问题,如果我的程序的input如a = 5,我会为a分配空间,或者在随后的a =“foo”上重新分配相同的空间。 这将保持分配,直到:

  1. 用户input'unset a'
  2. 我的清理function被input,要么服务信号,要么input“退出”

我不能想象任何现代操作系统,在一个进程退出后,不回收内存。 然后,免费()便宜,为什么不清理? 正如其他人所说的,像valgrind这样的工具非常适合发现你真正需要担心的漏洞。 即使你的例子块将被标记为'仍然可以',当你试图确保你没有泄漏只是在输出额外的噪音。

另一个神话是“ 如果它主要(),我不必释放它 ”,这是不正确的。 考虑以下几点:

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

如果在分叉/守护进程之前(并且理论上永远在运行),那么你的程序已经泄漏了一个不确定的大小255次。

一个好的,写得好的程序应该总是自行清理。 释放所有的内存,刷新所有的文件,closures所有的描述符,取消所有临时文件的链接等等。这个清理function应该在正常终止或者收到各种致命的信号时到达,除非你想留下一些文件,所以你可以检测到崩溃并恢复。

真的,善待可怜的灵魂,当你转移到其他的东西时,必须保持你的东西。把它交给他们'valgrind clean':)

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

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

但是如果我在一个没有操作系统的环境下呢? 哪里没有什么?

想象一下,现在你正在使用线程风格的中断和分配内存。 在C标准中ISO / IEC:9899是存储器的寿命表述为:

7.20.3内存pipe理function

1通过对calloc,malloc和realloc函数的连续调用分配的存储顺序和连续性未指定。 如果分配成功,返回的指针被适当地alignment,以便它可以被分配给任何types的对象的指针,然后用来访问分配的空间中的这样的对象或这样的对象的数组(直到空间被明确地解除分配) 。 分配对象的生命周期从分配延伸到释放。

所以不能认为环境正在为你腾出空间。 否则,它将被添加到最后一句:“或者直到程序终止。”

换句话说:不释放记忆不仅仅是坏习惯。 它产生非便携式而不是C符合的代码。 如果以下情况得到环境的支持,至less可以看作是“正确的”。

但在没有操作系统的情况下,没有人为你做这个工作(我通常知道你不会在embedded式系统上分配和重新分配内存,但有些情况下你可能会想要。)

因此,在普通的C语言(OP被标记)中,这只是产生了错误的和不可移植的代码。

离开的时候离开记忆是完全正常的; malloc()从称为“堆”的内存区域分配内存,当进程退出时,一个进程的完整堆被释放。

也就是说,人们仍然坚持认为在退出之前释放所有东西的好处的一个原因是,内存debugging器(例如Linux上的valgrind)会将未被阻塞的块作为内存泄漏进行检测,如果还有“真实”的内存泄漏,如果在最后也得到“假”结果,则更难以发现它们。

此代码通常可以正常工作,但考虑代码重用问题。

你可能已经写了一些不释放分配内存的代码片段,它的运行方式是内存自动回收。 看起来好吧。

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

代码重用在企业中是典型的。 通常,公司拥有其员工生产的所有代码,并且每个部门都可以重复使用该公司拥有的所有代码。 所以通过编写这样的“无辜的代码”,你可能会让别人头疼。 这可能会让你被解雇。

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

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

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

如果这个过程是短暂的,那么当这个过程完成时,所有分配的内存都被操作系统回收了,但是我build议养成所有没有进一步使用的内存的习惯。

这里真正的结果是什么?

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

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

但是,您依赖的是您不应该依赖的安全function,而且您的程序(或函数)可能会在系统中运行, 下次这种行为确实会导致“硬”内存泄漏。

您可能正在内核模式下运行,或者在没有使用内存保护的老式/embedded式操作系统上进行折衷。 (MMU占用空间,内存保护花费额外的CPU周期,并且从程序员那里请求自己清理也不算太多)。

您可以按照自己喜欢的方式使用和重新使用内存,但要确保在退出之前取消分配所有资源。

你是正确的,当进程退出时,内存被自动释放。 有些人在stream程终止的时候力争不做大量的清理工作,因为这些都将全部交给操作系统。 但是,当你的程序运行时,你应该释放不用的内存。 如果你不这样做,那么如果你的工作设置变得太大,你最终可能会用光或导致过度的分页。

你在这方面绝对正确。 在一些小程序中,variables必须存在直到程序死亡,释放内存没有真正的好处。

事实上,我曾经参与过一个项目,每个项目的执行过程都非常复杂,但是相对比较短暂,决定只是保持内存分配,而不是通过错误地分配项目来破坏项目的稳定。

这就是说,在大多数程序中,这不是一个真正的select,或者它可能导致你用尽内存。

如果您是从头开发一个应用程序,您可以做出一些关于何时免费通话的教育select。 你的示例程序是好的:它分配内存,也许你有它的工作几秒钟,然后closures,释放所有它声称的资源。

如果你正在编写其他任何东西 – 一个服务器/长时间运行的应用程序,或者一个被其他人使用的库,你应该期望在你所有的malloc中调用free。

忽视实用的一面,遵循更严格的方法更为安全,强迫自己释放你所有的东西。 如果你不习惯在编码时注意内存泄漏,那么你可能会轻易地发现一些泄漏。 所以换句话说,是的 – 你可以逃脱, 请小心,但。

在OSTEP在线教科书中实际上有一个操作系统本科课程中的一部分,其中讨论了您的问题。

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

在某些情况下,它可能看起来不是免费的()是合理的。 例如,你的程序是短命的,很快就会退出; 在这种情况下,当进程死亡时,操作系统将清理所有分配的页面,因此不会发生内存泄漏。 虽然这肯定是“有效的”(见第7页的旁白),但这可能是一个坏习惯,所以要谨慎select这样的策略

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

在幕后,操作系统将把用户看到的“虚拟地址”翻译成指向物理内存的实际地址。

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


编辑:在摘录中提到的一边复制下面。

除此之外为什么没有内存泄漏,一旦你的程序退出

当你编写一个短命的程序时,你可以使用malloc()分配一些空间。 程序运行并即将完成:是否需要退出前多次调用free() ? 虽然看起来不对,但没有任何记忆会被真正意义上的“迷失”。 原因很简单:系统中实际存在两级内存pipe理。 内存pipe理的第一级是由操作系统执行的,操作系统在运行时将内存传递给进程,并在进程退出(或以其他方式)时将其取回。 第二级pipe理在每个进程中,例如在你调用malloc()free()的堆内。 即使你没有调用free() (并因此泄漏了堆中的内存),操作系统也将回收该进程的所有内存(包括代码,堆栈的页面,以及相关的堆)完成运行。 无论您的地址空间中堆的状态如何,操作系统在进程死亡时都会收回所有这些页面,从而确保即使没有释放内存,也不会丢失内存。

因此,对于短期的程序,泄漏的内存通常不会造成任何操作问题(尽pipe可能被认为是不好的forms)。 当你编写一个长期运行的服务器(比如Web服务器或数据库pipe理系统,永远不会退出)时,泄漏的内存是一个更大的问题,并且最终会导致应用程序内存不足时崩溃。 当然,在一个特定的程序内部泄漏内存是一个更大的问题:操作系统本身。 再次向我们展示:编写内核代码的人员是最艰苦的工作。

从第7页的Memory API章节开始

操作系统:三个简单的部分
Remzi H. Arpaci-Dusseau和Andrea C. Arpaci-Dusseau Arpaci-Dusseau Books 2015年3月(版本0.90)

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

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