提问



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


我们都在学校(至少,我是)教过你必须释放所有分配的指针。但是,我有点好奇,关于不释放内存的实际成本。在一些明显的情况下,比如在循环内部调用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后永远自由是没有意义的,因为这些变量必须在程序执行期间始终存在,并且没有好的方法(我可以看到)用静态分配的空间来实现它。如果有一堆内存被分配但只是作为进程结束的一部分被释放,这是不好的设计吗?如果是这样,有什么替代方案?

最佳参考


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


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


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


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

其它参考1


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


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



??分配误区4:非垃圾收集程序
??应该总是释放所有记忆
??他们分配。

??
??真相:省略
??经常执行的解除分配
??代码导致泄漏增加。他们是
??很少接受。但程序
??保留最多分配的内存,直到
??程序退出通常表现更好
??没有任何干预解除分配。
??如果,Malloc更容易实现
??没有免费的。

??
??在大多数情况下,释放内存
??在程序退出之前没有意义。

??操作系统无论如何都会收回它。自由
??会触及死亡页面
??对象;操作系统赢得了t。

??
??结果:小心泄漏
??探测器计算分配。
??一些泄漏是好的!



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


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

其它参考2


=== 面向未来代码重用怎么样? ===


如果您编写代码以释放对象,那么您将限制代码只有在您可以依赖内存空闲时才能安全使用d正在关闭进程。即小型一次性使用项目或扔掉
int main()
{
    char *a = malloc(1024);
    /* Do some arbitrary stuff with 'a' (no alloc functions) */
    return 0;
}
项目)......您知道该过程何时结束。


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





int main()
{
    char *a = malloc(1024);
    /* Do some arbitrary stuff with 'a' (no alloc functions) */
    return 0;
}
关于丢弃项目。 扔掉项目中使用的代码有一种不被丢弃的方式。接下来你知道十年过去了,你的扔掉代码仍在使用中。



我听到一个关于一些人为了让他的硬件更好地工作而编写一些代码的故事。他说只是一个爱好,不会变得大而专业。多年后,很多人都在使用他的爱好代码。 [20]

其它参考3


你是对的,没有伤害,退出

更快

这有很多原因:



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

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

  • 更重要的是,在exit()之前完成它是浪费时间。退出时,内存页面和交换空间就会被释放。相比之下,一系列free()调用会烧掉CPU时间并导致磁盘分页操作,缓存未命中和缓存驱逐。



关于未来代码重用的 possiblility 证明无意义操作的确定性:这是一个考虑因素,但它可能不是敏捷方式。 YAGNI![21] [22]

其它参考4


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


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


但是,如果我处于一个我根本没有操作系统的环境中呢?
哪里什么都没有?


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



??7.20.3内存管理功能

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



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


换句话说:
不释放记忆不仅仅是不好的做法。它产生非便携而不符合C的代码。
至少可以将其视为正确,如果以下情况:[[......]],则受环境支持。


但是如果您根本没有操作系统,那么没有人为您完成这项工作
(我知道你通常不会在嵌入式系统上分配和重新分配内存,
?但有些情况下你可能想要。)


所以在普通C语言中(OP标记为),
?这只是产生错误和不可移植的代码。

其它参考5


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


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


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



  1. 用户键入取消设置

  2. 输入了我的清理功能,无论是为信号提供服务还是用户输入退出



我想不出任何现代操作系统在进程退出后不回收内存。然后,free()便宜,为什么不清理?正如其他人所说的那样,像valgrind这样的工具非常适合发现你确实需要担心的漏洞。即使您的示例块被标记为仍然可以访问,但当您尝试确保没有泄漏时,它只会在输出中产生额外的噪音。


另一个神话是如果它在main()中,我不必释放它,这是不正确的。请考虑以下事项:


char *t;

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


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


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


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

其它参考6


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


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

其它参考7


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


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

其它参考8


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


您可能已经编写了一些代码片段,它不会释放已分配的内存,但它会以一种自动回收内存的方式运行。似乎没事。


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


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

其它参考9



??这里真正的结果是什么?



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


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


但是你依赖的是一个你不应该依赖的安全功能,你的程序(或函数)可能会在这种行为 导致硬内存泄漏的系统上运行,下一步时间。


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


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

其它参考10


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


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

其它参考11


你是对的,进程退出时会自动释放内存。有些人在流程终止时努力不进行大量清理,因为它将全部放弃到操作系统。但是,当您的程序运行时,您应该释放未使用的内存。如果你不这样做,如果你的工作组太大,你最终可能会用完或引起过多的分页。

其它参考12


如果你从头开始开发应用程序,你可以做出一些有关何时免费调用的有选择的选择。你的示例程序很好:它分配内存,也许你让它工作几秒钟,然后关闭,释放所有资源它宣称。


如果您正在编写其他任何东西 - 服务器/长时间运行的应用程序,或者其他人使用的库,您应该期望在malloc的所有内容上免费调用。


忽略实用方面一秒钟,遵循更严格的方法更加安全,并强迫自己释放你所有的东西。如果你不习惯在编码时注意内存泄漏,你可以很容易地弹出一个泄漏很少。换句话说,是的 - 没有它你就可以逃脱;但请小心。

其它参考13


在这方面你是完全正确的。在小程序中,变量必须存在直到程序死亡,释放内存没有任何实际好处。


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


话虽这么说,在大多数程序中,这不是一个真正的选择,或者它可能导致你的内存耗尽。

其它参考14


OSTEP在线教科书实际上是一个关于操作系统本科课程的部分,它正好讨论了你的问题。[23]


相关部分是第6页的Memory API章节中的忘记释放内存,其中给出了以下解释:[24]



??在某些情况下,似乎没有调用free()是合理的。对于
??例如,你的程序是短暂的,很快就会退出; 在这种情况下,
??当进程终止时,操作系统将清理其所有已分配的页面
??因此本身不会发生内存泄漏。
虽然这肯定有效
??(见第7页的旁边),这可能是一个坏习惯,所以要小心谨慎
??选择这样的策略



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


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


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





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



??ASIDE:为什么在您的流程退出时没有内存泄漏

??
??当你编写一个短命的程序时,你可能会分配一些空间
??使用malloc()。程序运行并即将完成:是否存在
??在退出前需要多次调用free()?虽然看来
??不错,没有记忆会在任何真正意义上丢失。原因是
??简单:系统中实际上有两个级别的内存管理。
??第一级内存管理由OS执行
??在运行时将内存分发给进程,并在运行时将其取回
??进程退出(或以其他方式死亡)。第二级管理
??在每个进程中,例如在您调用时在堆内
??malloc()free()。即使你没有打电话free()(因而泄漏
??在堆内存中,操作系统将回收所有内存
??过程(包括那些代码,堆栈和相关页面)
??堆)程序运行完毕后。无论什么状态
??在你的地址空间堆中,操作系统会收回所有这些页面
??当进程死亡时,确保尽管没有内存丢失
??事实上,你没有释放它。

??
??因此,对于短命的程序,泄漏的内存通常不会导致任何问题
??操作问题(虽然它可能被视为不良形式)。什么时候
??你编写一个长期运行的服务器(如Web服务器或数据库管理
??系统,永不退出),泄露内存是一个更大的问题,
??当应用程序耗尽时,最终会导致崩溃
??记忆。当然,内存泄漏是一个更大的问题
??一个特定的程序:操作系统本身。向我们展示一次
??再次:那些编写内核代码的人拥有最艰巨的工作......

??
??

????来自 [25]的内存API章节第7页的

????
????操作系统:三个简单的部分

????Remzi H. Arpaci-Dusseau和Andrea C. Arpaci-Dusseau
????Arpaci-Dusseau Books
????2015年3月(版本0.90)[26]

??


其它参考15


如果程序忘记在退出操作系统之前释放几兆字节将释放它们。但是如果你的程序一次运行数周并且程序中的循环忘记在每次迭代中释放几个字节,那么你将会遇到强大的内存泄漏,这会占用计算机中所有可用内存,除非你重新启动它如果程序用于一项非常重要的任务,即使最初没有为一个人设计,即使很小的内存泄漏也可能是不好的。

其它参考16


我认为你的两个例子实际上只有一个:free()应该只在流程结束时发生,正如你所指出的那样,因为流程正在终止,所以没用。


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