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

Sco*_*ott 515 c malloc free

这已成为困扰我多年的事情.

我们都在学校(至少,我是)教过你必须释放每个分配的指针.不过,我有点好奇,关于不释放内存的实际成本.在一些明显的情况下,就像在malloc循环内部或线程执行的一部分中调用时一样,释放是非常重要的,因此没有内存泄漏.但请考虑以下两个例子:

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

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

这里真正的结果是什么?我的想法是,进程死了,然后堆空间无论如何都没有了,因此错过调用没有任何害处free(但是,我确实认识到无论如何都要将它关闭,可维护性和良好实践的重要性).我对这个想法是对的吗?

其次,假设我的程序有点像shell.用户可以声明类似的变量,aaa = 123并将其存储在某些动态数据结构中供以后使用.很明显,你可以使用一些解决方案来调用一些*alloc函数(hashmap,链表,类似的东西).对于这种程序,在调用之后永远自由是没有意义的,malloc因为这些变量必须在程序执行期间始终存在,并且没有好的方法(我可以看到)用静态分配的空间来实现它.拥有一堆已分配但仅作为流程结束的一部分释放的内存,这是不好的设计吗?如果是这样,有什么替代方案?

Pau*_*lin 361

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

所以一般来说,它没有任何害处,除了拥有比你需要的更多存储空间的运行时成本.当然,在您给出的示例中,您希望保留一个可能被使用的变量的内存,直到它被清除.

但是,只要你不再需要它就可以释放内存,并且在程序退出时释放你仍然拥有的任何东西.这更像是一种了解你正在使用的记忆,并思考你是否仍然需要它的练习.如果不跟踪,可能会发生内存泄漏.

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

  • "但是,只要你不再需要它就可以释放记忆,并且在程序退出时释放你仍然拥有的任何内容,这被认为是一种很好的风格." 那你错了吗? (113认同)
  • 我真的认为这个答案是错误的.一旦完成它们之后应该总是释放资源,无论是文件句柄/内存/互斥量.通过养成这种习惯,在构建服务器时不会犯这种错误.一些服务器预计将全天候运行.在这些情况下,任何类型的泄漏都意味着您的服务器最终会耗尽该资源并以某种方式挂起/崩溃.一个简短的实用程序,你的泄漏并不是那么糟糕.任何服务器,任何泄漏都是死亡.帮自己一个忙.自己清理干净.这是一个好习惯. (75认同)
  • @Paul - 只是同意EvilTeach,被认为是免费记忆的好风格,不释放内存是不正确的.你的措辞使得这看起来和佩戴与领​​带相匹配的手帕一样重要.实际上,这是穿着裤子的水平. (26认同)
  • 如果你有一个直到程序退出所需的内存存储,并且你没有在原始操作系统上运行,那么在退出之前释放内存是一种风格选择,而不是缺陷. (23认同)
  • 如果有人拿你的程序(并且它仍然运行在不恢复内存的操作系统上)运行它然后GG,那么可能还可以提到并非所有人都使用现代操作系统. (15认同)
  • 最后一段是完全错误的.在C语言中,从`main`返回时正常程序终止或从`main`返回时隐式`exit`关闭并刷新所有打开的文件. (8认同)
  • 这不是一个样式问题,它是一个程序缺陷.事实上,在许多情况下,泄漏是沉默的,并不会神奇地将其变成无缺陷. (7认同)
  • @Heath Hunnicutt:Mac OS X Snow Leopard和Lion都使用`kill -9`来关闭保存了所有文件的程序.这种方式要快得多,让操作系统清理,而不是等待程序关闭他们的东西. (6认同)
  • @Ahmad实际上我更关心文件缓冲区没有被刷新,数据库事务没有被提交,以及那种事情.就像我相信我上面说过的那样,我编程的每个操作系统都会在程序退出时回收内存,但我对嵌入式系统等不太确定. (5认同)
  • @Heath Hunnicutt:http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/NSProcessInfo_Class/Reference/Reference.html#//apple_ref/doc/uid/TP40003715 (3认同)
  • 我不同意你应该"免除你在程序退出时仍然存在的任何东西." Raymond Chen说得对:http://blogs.msdn.com/b/oldnewthing/archive/2012/01/05/10253268.aspx此外,Firefox目前在关机时释放内存,但正在努力调用exit(0) :https://bugzilla.mozilla.org/show_bug.cgi?id = 662444作为Firefox的用户,我期待他们在退出时停止释放内存的那一天.这很浪费,应该气馁. (3认同)
  • @paulm我专门处理OP所询问的内容 - 如果程序退出后有任何后果.据我所知,对任何现代操作系统都没有任何影响,但我不会因为存在一些不明显的嵌入式环境而对我的声誉产生影响. (3认同)
  • https://devblogs.microsoft.com/oldnewthing/20120105-00/?p=8683 (3认同)
  • @Zan - 你有参考吗?听起来这是一个糟糕的主意,你的陈述很难让我相信. (2认同)
  • @Heath Hunnicutt:寻找"突然终止" (2认同)
  • 我会提到它可能导致由于堆碎片导致的alloc失败,即使你只泄漏1个字节.当缓冲区溢出进入泄漏的缓冲区时,它可以掩盖堆损坏. (2认同)
  • @nirvanaswap我想这取决于析构函数是否做一些具体的事情,例如将缓冲区刷新到磁盘或提交数据库事务。就我个人而言,我不会将这些东西放在析构函数中,但是有些人会这么做。 (2认同)
  • 假设这是在现代操作系统上运行,正如 @PaulTomblin 所说,那么当然应该将 free 作为其良好实践,但由于过于挑剔,有人可能会认为添加 free() 会像 O/ 一样稍微减慢系统速度S 首先必须收集 1K 缓冲区,运行任何内存管理算法来清除/管理堆,然后在程序关闭时清除整个堆。而没有 free,它只会释放整个堆。 (2认同)

com*_*pie 107

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

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

分配误区4:非垃圾收集程序应该始终释放它们分配的所有内存.

真相:经常执行的代码中省略的解除分配会导致泄漏增加.它们很少被接受.但是在程序退出之前保留最多分配内存的程序通常执行得更好而没有任何干预重新分配.如果没有免费的话,Malloc更容易实现.

在大多数情况下,在程序退出之前释放内存是没有意义的. 操作系统无论如何都会收回它.免费将触及死亡对象中的页面; 操作系统不会.

结果:小心计算分配的"泄漏检测器".一些"泄漏"是好的!

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

第二个问题:你的设计还可以.如果您需要在应用程序退出之前存储某些内容,那么可以通过动态内存分配来执行此操作.如果您不知道所需的大小,则不能使用静态分配的内存.

  • 我认为通过说"因为检漏仪"来解释需要释放内存是错误的.这就像是说"你必须在游戏街慢慢开车,因为警察可以用高速摄像机等你". (8认同)
  • 事实上,即使在长时间运行的程序中,一次性的中等大小的泄漏也不是问题。(强调“一次性”部分。)但是,清理它仍然是最佳实践,这样验证器就不会抱怨 - 并不是因为关闭验证器本身很有用,而是因为如果您验证输出中有一堆“可接受的”失败,那么找到“不可接受的”失败要困难得多。 (6认同)
  • 可能是因为我读到的问题是泄露的内存实际发生的问题,而不是这个具体的例子是否合适.我不会投票,因为这仍然是一个很好的答案. (3认同)
  • 可能有(早期的Windows,早期的Mac OS),或许仍然是,操作系统要求进程在退出之前释放内存,否则空间不会被回收. (3认同)

Tre*_*ith 56

=== 未来的验证代码重用怎么?===

如果你没有编写代码来释放对象,那么你就是将代码限制为只有在你可以依赖被关闭进程释放的内存时才能安全使用...即小的一次性使用项目或"扔掉" [1]项目)...你知道什么时候过程结束.

如果您确实编写了free()s所有动态分配内存的代码,那么您将来可以验证代码并让其他人在更大的项目中使用它.


[1]关于"扔掉"项目."投掷"项目中使用的代码有一种不被丢弃的方式.接下来你知道十年过去了,你的"扔掉"代码仍在使用中.

我听到一个故事讲述了一些人为了让他的硬件更好地工作而编写了一些代码.他说" 只是一个爱好,不会大而专业 ".多年以后,很多人都在使用他的"爱好"代码.

  • 赞成"小项目".有许多大型项目在退出时非常有意*不释放内存,因为如果您了解目标平台,则浪费时间.国际海事组织,一个更准确的例子就是"孤立的项目".例如,如果您正在创建一个可以包含在其他应用程序中的可重用库,则没有明确定义的退出点,因此您不应该泄漏内存.对于独立应用程序,您将始终确切知道进程何时结束,并且可以有意识地决定将清理卸载到操作系统(必须以任何方式进行检查). (7认同)
  • 昨天的应用程序是今天的库函数,明天它将被链接到一个长期存在的服务器,调用它数千次。 (3认同)
  • @AdrianMcCarthy:如果一个函数检查静态指针是否为空,如果是,则用“malloc()”初始化它,如果指针仍然为空则终止,这样的函数可以安全地使用任意次数,即使“ free` 永远不会被调用。我认为区分可能会耗尽无限量存储空间的内存泄漏与只能浪费有限且可预测的存储量的情况可能是值得的。 (2认同)
  • @AdrianMcCarthy:更改代码以不再使用静态指针可能需要将指针移动到某种“上下文”对象中,并添加代码来创建和销毁此类对象。如果不存在分配时指针始终为“null”,并且当存在分配时指针始终为“null”,那么让代码释放分配并在上下文被销毁时将指针设置为“null”将很简单,尤其是与其他所有内容相比将静态对象移动到上下文结构中需要这样做。 (2认同)

Dig*_*oss 49

你是对的,不会造成任何伤害,退出的速度会更快

这有多种原因:

  • 所有桌面和服务器环境都只是在exit()上释放整个内存空间.他们不了解程序内部数据结构,如堆.

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

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

关于possiblility未来代码重用的justifing的确定性毫无意义的OPS的:这是一个考虑因素,但它无疑不是敏捷的方式.YAGNI!

  • YAGNI 原则是双向的:您永远不需要优化关闭路径。过早的优化等等。 (4认同)
  • 没关系,找到答案:http://stackoverflow.com/questions/1421491/does-calling-free-or-delete-ever-release-memory-back-to-the-system.谢谢你这么! (3认同)
  • 我曾经在一个项目上工作,我们花了很少的时间试图了解程序的内存使用情况(我们需要支持它,我们没有写它).根据我的经验,我有同感你的第二颗子弹.但是,我想听听你(或某人)提供更多证据证明这是真的. (2认同)

Tim*_*ost 23

一旦我确定我已完成它,我通常会释放每个已分配的块.今天,我的程序的入口点可能是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);
}
Run Code Online (Sandbox Code Playgroud)

如果在分叉/守护之前(理论上永远运行),你的程序刚刚泄漏了不确定大小的t 255次.

一个好的,写得很好的程序应该始终清理干净.释放所有内存,清除所有文件,关闭所有描述符,取消所有临时文件的链接等.这个清除功能应该在正常终止时或在收到各种致命信号时到达,除非你想留下一些文件,这样你就可以检测崩溃并恢复.

真的,善待那些在你搬到其他东西时必须保持你的东西的可怜的灵魂......把它交给他们'valgrind clean':)

  • @LieRyan如果你有十亿,就像十亿个十亿结构一样,你最明显还有其他问题需要特殊程度的考虑 - 超出这个特定答案的范围:) (3认同)

dhe*_*ein 23

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

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

但是,如果我在一个我根本没有操作系统的环境中呢?什么地方没有?

想象一下,现在您正在使用线程样式的中断并分配内存.在C标准中ISO/IEC:9899是内存的生命周期,表示为:

7.20.3内存管理功能

1未指定连续调用calloc,malloc和realloc函数分配的存储的顺序和连续性.如果分配成功,则返回的指针被适当地对齐,以便可以将其指定给指向任何类型对象的指针,然后用于在分配的空间中访问此类对象或此类对象的数组(直到空间被显式释放) .分配对象的生命周期从分配延伸到解除分配.[...]

因此,不能给出环境正在为您解放的工作.否则它将被添加到最后一句:"或直到程序终止."

换句话说:不释放记忆不仅仅是不好的做法.它产生非便携而不符合C的代码.哪个至少可以被视为'正确,如果以下:[...],由环境支持'.

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

因此,一般来说,普通C(OP被标记),这只是产生错误和不可移植的代码.

  • -1 用于引用 C 标准,而其中大部分在没有操作系统的情况下不适用,因为没有运行时来提供标准要求的功能,尤其是关于内存管理和标准库函数(显然也没有这些功能)以及运行时/操作系统)。 (4认同)
  • 一个反驳的观点是,如果你是一个嵌入式环境,那么你 - 作为开发者 - 首先会更加挑剔你的内存管理.通常,这实际上是预先分配静态固定内存而不是任何运行时mallocs/reallocs. (3认同)
  • @lunarplasma:虽然你所说的并没有错,但这并不能改变语言标准所陈述的事实,并且每个反对/进一步发展它的人,甚至可能是常识,都在生产有限的代码。如果有人说“我不必关心它”,我可以理解,因为有足够的案例可以解决。但是那个人至少应该知道为什么他不必关心。尤其不要省略它,只要问题与那个特殊情况无关。而且由于 OP 在理论(学校)方面一般询问 C。说“你不需要”是不行的! (2认同)
  • 在大多数没有操作系统的环境中,程序无法“终止”。 (2认同)
  • @supercat:正如我之前所写:你是对的。但如果有人问起教学原因和学校方面的问题,就不能说“你不需要考虑它,因为大多数时候这并不重要”。语言的措辞和行为给出定义是有原因的,仅仅因为大多数环境都为你处理它,你不能说没有必要关心。这就是我的观点。 (2认同)
  • @Nax:你能告诉我标准在哪里明确规定了这一原则作为强制性要求吗?问题是标准只是简单地说明了什么环境必须提供什么机制以及实施它的工作是谁。像内存管理这样的事情甚至没有被谈论过。即使标准明确考虑了操作系统的缺失,因为它对每个功能都进行了说明,如果这是编译器的工作,操作系统或者根本不强制实现特定功能。其编写方式是确保即使在没有操作系统的情况下该标准也可应用 (2认同)

Ant*_*ima 13

当你退出时,留下记忆是完全没问题的; malloc()从称为"堆"的内存区域分配内存,并在进程退出时释放进程的完整堆.

话虽这么说,人们仍然坚持认为在退出之前释放所有内容是好的一个原因是内存调试器(例如Linux上的valgrind)检测到不同的块作为内存泄漏,如果你还有"真正的"内存泄漏,它就会变成如果你最后得到"假"结果,更难发现它们.

  • -1表示"完全正常"在不释放它的情况下留下已分配的内存是不好的编码习惯.如果该代码被提取到库中,那么它将导致整个地方的memleaks. (11认同)
  • +1补偿.请参阅compie的答案.'退出'时的`free`被认为是有害的. (5认同)

Bil*_*ard 11

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

编辑:说其他正在运行的程序被剥夺了内存并不是100%准确.操作系统总是允许他们使用它,代价是将程序交换到虚拟内存(</handwaving>).但重点是,如果您的程序释放了不使用的内存,则不太可能需要虚拟内存交换.


sha*_*oth 11

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

您可能已经编写了一些不释放已分配内存的代码片段,它以这样的方式运行,然后自动回收内存.似乎还好.

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

代码重用在企业中很常见.通常,公司拥有其员工生产的所有代码,每个部门都可以重复使用公司拥有的任何代码.因此,通过编写这种"天真无邪"的代码,您可能会对其他人造成潜在的麻烦.这可能会让你被解雇.

  • 这可能是值得一提的不只是一个人复制片段的可能性,但也被修改反复做被写入做一些特定的动作,一旦一个程序的可能性.在这种情况下,将内存分配*一次*然后重复使用而不会被释放是没关系的,但为每个操作分配和放弃内存(不释放它)可能是灾难性的. (2认同)

Dev*_*lar 6

这里真正的结果是什么?

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

大多数现代桌面操作系统都会在进程终止时恢复泄露的内存,这使得忽略该问题变得非常常见,这可以从许多其他答案中看出.)

但是,你是靠一种安全功能,你不应该依赖,和你的程序(或功能)可能一个系统,这个行为上运行导致"硬"内存泄漏,旁边的时间.

您可能在内核模式下运行,或者在不使用内存保护作为权衡的老式/嵌入式操作系统上运行.(MMU占用芯片空间,内存保护需要额外的CPU周期,并且要求程序员在自己之后进行清理并不算太多).

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

  • 在 Amiga exec.library 中,使用 AllocMem() 后不调用 Free() 将使内存“丢失”,直到重新启动为止,malloc 和 free 将在幕后使用这些内存。 (2认同)

Kyl*_*nin 5

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

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

  • @KyleCronin 我“非常”宁愿有段错误而不是内存泄漏,因为两者都是严重的错误,并且段错误更容易检测。内存泄漏常常被忽视或未解决,因为它们“非常良性”。我和我的 RAM 完全不同意。 (2认同)

spe*_*yue 5

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

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

在某些情况下,似乎不调用free()是合理的。例如,您的程序寿命很短,将很快退出;在这种情况下,当进程终止时,操作系统将清理其所有分配的页面,因此不会发生内存泄漏。尽管这肯定是“有效的”(请参阅​​第7页的旁白),但养成这种习惯可能是个坏习惯,因此请谨慎选择这种策略

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

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

但是,共享资源(例如物理内存)要求操作系统跟踪正在使用哪些进程。因此,如果进程终止,则回收操作系统的内存在操作系统的能力和设计目标之内,以便它可以重新分配内存并与其他进程共享。


编辑:摘录中提到的一旁复制如下。

旁白为什么程序退出后不会留下记忆

在编写短期程序时,您可以使用分配一些空间malloc()。该程序将运行并即将完成:是否需要free()在退出前多次调用?尽管这样做似乎是错误的,但实际上任何内存都不会“丢失”。原因很简单:系统中实际上有两个级别的内存管理。操作系统执行内存管理的第一级,操作系统在运行时将内存分配给进程,并在进程退出(或以其他方式终止)时将其收回。第二层管理在每个进程内,例如,在调用malloc()和时在堆内 free()。即使你没打电话free()(并因此导致堆中的内存泄漏),操作系统将在程序完成运行时回收进程的所有内存(包括代码,堆栈和相应的堆页面)。无论您的地址空间中的堆状态如何,当进程终止时,操作系统都会收回所有这些页面,从而确保即使您没有释放它也不会丢失任何内存。

因此,对于寿命短的程序,内存泄漏通常不会引起任何操作问题(尽管它可能被认为是较差的形式)。当您编写长时间运行的服务器(例如Web服务器或数据库管理系统,这些服务器永不退出)时,内存泄漏是一个更大的问题,当应用程序内存不足时,最终将导致崩溃。当然,内存泄漏是一个特定程序(操作系统本身)内更大的问题。再次向我们展示:编写内核代码的人工作最艰苦……

从第7页的“ 内存API”一章

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