这已成为困扰我多年的事情.
我们都在学校(至少,我是)教过你必须释放每个分配的指针.不过,我有点好奇,关于不释放内存的实际成本.在一些明显的情况下,就像在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),那么在完成对象时不释放对象将意味着析构函数永远不会被调用,并且类负责的任何资源都可能无法清除.
是的,你是对的,你的例子不会造成任何伤害(至少在大多数现代操作系统上都没有).进程退出后,操作系统将恢复进程分配的所有内存.
来源:分配和GC神话(PostScript警报!)
分配误区4:非垃圾收集程序应该始终释放它们分配的所有内存.
真相:经常执行的代码中省略的解除分配会导致泄漏增加.它们很少被接受.但是在程序退出之前保留最多分配内存的程序通常执行得更好而没有任何干预重新分配.如果没有免费的话,Malloc更容易实现.
在大多数情况下,在程序退出之前释放内存是没有意义的. 操作系统无论如何都会收回它.免费将触及死亡对象中的页面; 操作系统不会.
结果:小心计算分配的"泄漏检测器".一些"泄漏"是好的!
也就是说,你应该尽量避免所有内存泄漏!
第二个问题:你的设计还可以.如果您需要在应用程序退出之前存储某些内容,那么可以通过动态内存分配来执行此操作.如果您不知道所需的大小,则不能使用静态分配的内存.
=== 未来的验证和代码重用怎么样?===
如果你没有编写代码来释放对象,那么你就是将代码限制为只有在你可以依赖被关闭进程释放的内存时才能安全使用...即小的一次性使用项目或"扔掉" [1]项目)...你知道什么时候过程结束.
如果您确实编写了free()s所有动态分配内存的代码,那么您将来可以验证代码并让其他人在更大的项目中使用它.
[1]关于"扔掉"项目."投掷"项目中使用的代码有一种不被丢弃的方式.接下来你知道十年过去了,你的"扔掉"代码仍在使用中.
我听到一个故事讲述了一些人为了让他的硬件更好地工作而编写了一些代码.他说" 只是一个爱好,不会大而专业 ".多年以后,很多人都在使用他的"爱好"代码.
这有多种原因:
所有桌面和服务器环境都只是在exit()上释放整个内存空间.他们不了解程序内部数据结构,如堆.
几乎所有free()
实现都不会将内存返回给操作系统.
更重要的是,在exit()之前完成操作是浪费时间.在退出时,简单地释放内存页面和交换空间.相比之下,一系列free()调用将耗尽CPU时间,并可能导致磁盘分页操作,缓存未命中和缓存驱逐.
关于possiblility未来代码重用的justifing的确定性毫无意义的OPS的:这是一个考虑因素,但它无疑不是敏捷的方式.YAGNI!
一旦我确定我已完成它,我通常会释放每个已分配的块.今天,我的程序的入口点可能是main(int argc, char *argv[])
,但明天它可能会被foo_entry_point(char **args, struct foo *f)
输入为函数指针.
所以,如果发生这种情况,我现在有泄漏.
关于你的第二个问题,如果我的程序输入a = 5,我会为a分配空间,或者在随后的a ="foo"上重新分配相同的空间.这将保持分配,直到:
用户输入'unset a'
输入了我的清理功能,要么是为信号提供服务,要么是用户输入"退出"
我想不出任何现代操作系统在进程退出后不回收内存.再说一次,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':)
我完全不同意那些说OP正确或没有伤害的人.
每个人都在谈论现代和/或传统的操作系统.
但是,如果我在一个我根本没有操作系统的环境中呢?什么地方没有?
想象一下,现在您正在使用线程样式的中断并分配内存.在C标准中ISO/IEC:9899是内存的生命周期,表示为:
7.20.3内存管理功能
1未指定连续调用calloc,malloc和realloc函数分配的存储的顺序和连续性.如果分配成功,则返回的指针被适当地对齐,以便可以将其指定给指向任何类型对象的指针,然后用于在分配的空间中访问此类对象或此类对象的数组(直到空间被显式释放) .分配对象的生命周期从分配延伸到解除分配.[...]
因此,不能给出环境正在为您解放的工作.否则它将被添加到最后一句:"或直到程序终止."
换句话说:不释放记忆不仅仅是不好的做法.它产生非便携而不符合C的代码.哪个至少可以被视为'正确,如果以下:[...],由环境支持'.
但是在你根本没有操作系统的情况下,没有人为你做这项工作(我知道你通常不会在嵌入式系统上分配和重新分配内存,但在某些情况下你可能想要这样做.)
因此,一般来说,普通C(OP被标记),这只是产生错误和不可移植的代码.
当你退出时,留下记忆是完全没问题的; malloc()从称为"堆"的内存区域分配内存,并在进程退出时释放进程的完整堆.
话虽这么说,人们仍然坚持认为在退出之前释放所有内容是好的一个原因是内存调试器(例如Linux上的valgrind)检测到不同的块作为内存泄漏,如果你还有"真正的"内存泄漏,它就会变成如果你最后得到"假"结果,更难发现它们.
如果您正在使用已分配的内存,那么您没有做错任何事情.当您编写在不释放内存的情况下分配内存的函数(除了main之外)并且不将其提供给程序的其余部分时,它就会成为一个问题.然后你的程序继续运行分配给它的内存,但无法使用它.您的程序和其他正在运行的程序被剥夺了内存.
编辑:说其他正在运行的程序被剥夺了内存并不是100%准确.操作系统总是允许他们使用它,代价是将程序交换到虚拟内存().但重点是,如果您的程序释放了不使用的内存,则不太可能需要虚拟内存交换.
此代码通常可以正常工作,但请考虑代码重用的问题.
您可能已经编写了一些不释放已分配内存的代码片段,它以这样的方式运行,然后自动回收内存.似乎还好.
然后,其他人将您的代码段复制到他的项目中,使其每秒执行一千次.那个人现在在他的程序中有一个巨大的内存泄漏.一般来说不是很好,通常对服务器应用程序是致命的.
代码重用在企业中很常见.通常,公司拥有其员工生产的所有代码,每个部门都可以重复使用公司拥有的任何代码.因此,通过编写这种"天真无邪"的代码,您可能会对其他人造成潜在的麻烦.这可能会让你被解雇.
这里真正的结果是什么?
你的程序泄漏了内存.根据您的操作系统,它可能已被恢复.
大多数现代桌面操作系统都会在进程终止时恢复泄露的内存,这使得忽略该问题变得非常常见,这可以从许多其他答案中看出.)
但是,你是靠一种安全功能,你不应该依赖,和你的程序(或功能)可能一个系统,这个行为上运行并导致"硬"内存泄漏,旁边的时间.
您可能在内核模式下运行,或者在不使用内存保护作为权衡的老式/嵌入式操作系统上运行.(MMU占用芯片空间,内存保护需要额外的CPU周期,并且要求程序员在自己之后进行清理并不算太多).
您可以按照自己喜欢的方式使用和重用内存,但请确保在退出之前取消分配所有资源.
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版)