在C或C++应用程序中发生内存泄漏是否可以接受?
如果您分配一些内存并使用它直到应用程序中最后一行代码(例如,全局对象的析构函数),该怎么办?只要内存消耗不会随着时间的推移而增长,当您的应用程序终止时(在Windows,Mac和Linux上),是否可以信任操作系统为您释放内存?如果内存被连续使用直到它被操作系统释放,你甚至会认为这是一个真正的内存泄漏.
如果第三方图书馆强迫你这样做怎么办?拒绝使用第三方图书馆,无论它有多么伟大?
我只看到一个实际的缺点,那就是这些良性泄漏会将内存泄漏检测工具显示为误报.
没有.
作为专业人士,我们不应该问自己的问题是,"这样做是否可行?" 但更确切地说"有这么好的理由吗?" 而"追捕内存泄漏是一种痛苦"并不是一个好理由.
我喜欢简单易懂.简单的规则是我的程序应该没有内存泄漏.
这也让我的生活变得简单.如果我检测到内存泄漏,我会消除它,而不是通过一些复杂的决策树结构来确定它是否是"可接受的"内存泄漏.
它与编译器警告类似 - 警告对我的特定应用程序是否致命?也许不吧.
但这最终是专业纪律的问题.容忍编译器警告和容忍内存泄漏是一个坏习惯,最终会让我陷入困境.
为了使事情变得极端,外科医生是否可以在患者体内留下一些操作设备?
虽然有可能出现这样的情况,即移除该设备的成本/风险超过了将其丢弃的成本/风险,并且可能存在无害的情况,如果我在SurgeonOverflow.com上发布了这个问题除了"不"之外,还有任何答案,这会严重损害我对医学界的信心.
-
如果第三方图书馆强迫我这样做,那将导致我严重怀疑图书馆的整体质量.这就好像我试驾开车,并在其中一个杯架上发现了一些松散的垫圈和螺母 - 这本身可能不是什么大问题,但它描述了对质量缺乏承诺,所以我会考虑替代品.
除非"使用"的内存量不断增长,否则我不认为它是内存泄漏.除非所需的内存量不断增长,否则有一些未释放的内存虽然不理想但不是一个大问题.
首先让我们的定义正确.记忆泄漏是指内存动态分配时,例如malloc()
,内存丢失,所有对内存的引用都会丢失而没有相应的空闲内存.制作一个的简单方法是这样的:
#define BLK ((size_t)1024) while(1){ void * vp = malloc(BLK); }
注意,每次while(1)循环,分配1024(+开销)字节,并将新地址分配给vp; 没有剩余的指针指向前面的malloc'ed块.保证该程序运行直到堆耗尽,并且无法恢复任何malloc内存.内存从堆中"泄漏",永远不会被再次看到.
你所描述的,听起来像是
int main(){ void * vp = malloc(LOTS); // Go do something useful return 0; }
您分配内存,使用它直到程序终止.这不是内存泄漏; 它不会损害程序,当程序终止时,所有内存都将自动清除.
通常,您应该避免内存泄漏.首先,因为像你上面的高度和机库的燃料一样,泄漏并且无法恢复的记忆是无用的; 第二,在开始时正确编码,而不是泄漏内存比在以后发现内存泄漏要容易得多.
理论上没有,在实践中它取决于.
这实际上取决于程序正在处理多少数据,程序运行的频率以及程序是否在不断运行.
如果我有一个快速程序读取少量数据进行计算并退出,则永远不会注意到小内存泄漏.因为程序运行时间很长并且只使用少量内存,所以当程序存在时,泄漏会很小并释放.
另一方面,如果我有一个处理数百万条记录并运行很长时间的程序,那么一个小的内存泄漏可能会在给定足够时间的情况下降低机器.
对于有泄漏的第三方库,如果它们导致问题,要么修复库,要么找到更好的替代方案.如果它没有引起问题,它真的重要吗?
很多人似乎都认为,一旦你释放了内存,它就会立即返回到操作系统,并可以被其他程序使用.
事实并非如此.操作系统通常以4KiB页面管理存储器.malloc
和其他类型的内存管理从操作系统获取页面并在他们认为合适时对其进行子管理.这很可能free()
将不会返回页面中的操作系统,你的程序将在以后的malloc更多的内存的假设下.
我不是说free()
永远不会将内存返回给操作系统.它可能会发生,特别是如果你释放大量的内存.但是没有保证.
重要的事实是:如果你没有释放不再需要的内存,那么进一步的mallocs肯定会消耗更多的内存.但是如果你先释放,malloc可能会重新使用释放的内存.
这在实践中意味着什么?这意味着如果你知道你的程序从现在开始不需要更多的内存(例如它在清理阶段),释放内存并不是那么重要.但是,如果程序稍后可能会分配更多内存,则应避免内存泄漏 - 尤其是可能重复发生的内存泄漏.
另请参阅此注释以获取有关在终止错误之前释放内存的原因的更多详细信息.
评论者似乎并不理解调用free()
不会自动允许其他程序使用释放的内存.但这就是这个答案的全部要点!
因此,为了说服人们,我将展示一个free()没有什么好处的例子.为了使数学易于理解,我假装操作系统管理4000字节页面的内存.
假设您分配了一万个100字节的块(为简单起见,我将忽略管理这些分配所需的额外内存).这消耗1MB或250页.如果随后随机释放了9000个这样的块,那么你只剩下1000块 - 但它们遍布整个地方.据统计,大约有5个页面是空的.其他245将分别具有至少一个分配的块.这相当于980KB的内存,操作系统无法回收 - 即使你现在只分配了100KB!
另一方面,你可以现在malloc()9000多个块,而不会增加程序占用的内存量.
即使技术上free()
可以将内存返回给操作系统,也可能不会这样做.需要在快速操作和节省内存之间取得平衡.此外,已经分配了大量内存然后释放它的程序可能会再次这样做.Web服务器需要在请求后请求后处理请求 - 保留一些"松弛"内存是有意义的,这样您就不需要一直询问操作系统内存.free()
在应用程序运行后清除操作系统没有任何概念上的错误.
这实际上取决于应用程序及其运行方式.需要运行数周的应用程序中持续发生的泄漏必须得到解决,但是计算结果而没有太高内存需求的小工具应该不是问题.
有许多脚本语言不会垃圾收集循环引用的原因......对于它们的使用模式,它不是一个实际的问题,因此与浪费的内存一样浪费资源.
我相信答案是否定的,绝不允许内存泄漏,我有几个原因,我没有明确说明.这里有很好的技术答案,但我认为真正的答案取决于更多的社会/人类原因.
(首先,请注意,正如其他人所提到的,真正的泄漏是当你的程序在任何时候都失去了它已经分配的内存资源的跟踪.在C中,这发生在你malloc()
指向一个指针并让指针离开范围而没有做free()
第一.)
你这里决定的重要关键是习惯.当您使用指针编写语言时,您将使用很多指针.指针很危险; 它们是向代码中添加各种严重问题的最简单方法.
当你编码时,有时候你会在球上,有时候你会感到疲倦,生气或担心.在那些有点分心的时候,你在自动驾驶仪上编码更多.自动驾驶仪效果不区分一次性代码和较大项目中的模块.在这段时间内,您建立的习惯将最终落入您的代码库中.
所以不,永远不要让内存泄漏的原因与你改变车道时仍应检查盲点的原因相同,即使你现在是路上唯一的车.在你的活跃大脑分散注意力的时候,良好的习惯可以帮助你避免灾难性的失误.
除了"习惯"问题,指针很复杂,通常需要大量的脑力才能在心理上进行跟踪.当你使用指针时,最好不要"浑水",尤其是当你刚接触编程时.
还有更多的社交方面.通过正确使用malloc()
和free()
,任何查看代码的人都会放心; 你正在管理你的资源.但是,如果不这样做,他们会立即怀疑有问题.
也许你已经知道内存泄漏在这种情况下不会伤害任何东西,但是当你读取那段代码时,代码的每个维护者都必须在脑子里工作.通过使用free()
您删除甚至考虑问题的需要.
最后,编程是将一个过程的心理模型写成一个明确的语言,这样一个人和一个计算机就能完全理解所述过程.良好的编程实践的一个重要部分是永远不会引入不必要的歧义.
智能编程灵活且通用.错误的编程是模棱两可的.
我认为在你的情况下答案可能是没关系.但你肯定需要记录内存泄漏是一个有意识的决定.您不希望维护程序员出现,将您的代码插入函数内,并将其调用一百万次.因此,如果您决定泄漏是可以的,那么您需要为将来可能需要处理该程序的任何人记录(大写字母).
如果这是第三方库,您可能会被困.但绝对可以证明这种泄漏发生了.
但基本上如果内存泄漏是一个已知的数量,如512 KB缓冲区或其他什么,那么这是一个非问题.如果内存泄漏持续增长,就像每次调用库调用时,内存增加512KB并且没有释放,那么您可能会遇到问题.如果您记录并控制呼叫执行的次数,则可以进行管理.但是你真的需要文档,因为512并不多,512个超过一百万个电话是很多.
您还需要检查操作系统文档.如果这是一个嵌入式设备,可能会有一些操作系统无法从退出的程序中释放所有内存.我不确定,也许这不是真的.但值得研究.
我将给出不受欢迎但实用的答案,即释放内存总是错误的,除非这样做会减少程序的内存使用量.例如,一个程序进行单个分配或一系列分配以加载它将在其整个生命周期中使用的数据集,而不需要释放任何东西.在一个具有非常动态内存要求的大型程序(想想一个Web浏览器)的更常见的情况下,你应该尽快释放你不再使用的内存(例如关闭标签/文档/等). ,但是当用户选择点击"退出"时,没有理由释放任何内容,这样做实际上对用户体验有害.
为什么?释放内存需要触摸内存.即使你的系统的malloc实现没有将元数据存储在分配的内存块附近,你也可能会走向递归结构,只是为了找到你需要释放的所有指针.
现在,假设您的程序使用了大量数据,但暂时没有触及大部分数据(再次,Web浏览器就是一个很好的例子).如果用户正在运行大量应用程序,那么很大一部分数据可能已交换到磁盘.如果您只是退出(0)或从主页返回,它会立即退出.出色的用户体验.如果您在尝试释放所有内容时遇到麻烦,您可能会花费5秒或更长时间将所有数据交换回来,然后立即将其丢弃.浪费用户的时间.浪费笔记本电脑的电池寿命.浪费在硬盘上.
这不仅仅是理论上的.每当我发现自己加载的应用程序太多并且磁盘开始颠簸时,我甚至不会考虑单击"退出".我尽可能快地到达终端并键入killall -9 ......因为我知道"退出"会让情况变得更糟.
我确信有人可以提出说是的理由,但不会是我.而不是拒绝,我会说这不应该是一个是/否的问题.有办法管理或包含内存泄漏,许多系统都有它们.
在离开地球的设备上有NASA系统可以为此做出规划.系统将每隔一段时间自动重启,以便内存泄漏不会对整个操作造成致命的影响.只是遏制的一个例子.
如果您分配内存并使用它直到程序的最后一行,那不是泄漏.如果您分配内存并忘记它,即使内存量没有增长,这也是一个问题.分配但未使用的内存可能会导致其他程序运行速度变慢或根本不运行.
我可以一方面指望一段时间以来我所看到的"良性"泄漏的数量.
所以答案是非常合格的.
一个例子.如果你有一个单例资源,需要一个缓冲区来存储一个循环队列或双端队列,但不知道缓冲区需要多大,并且无法承担锁定或每个读取器的开销,那么分配一个指数倍增的缓冲区但是不释放旧的会泄漏每个队列/双端队列的大量内存.这些的好处是它们可以显着加快每次访问的速度,并且可以通过永远不存在争用锁定的风险来改变多处理器解决方案的渐近性.
我已经看到这种方法对于具有非常明确固定计数的事物非常有利,例如每CPU工作窃取deques,并且在用于保存单个/proc/self/maps
状态的缓冲区中,在Hans Boehm的保守垃圾收集器中用于C的程度要小得多/ C++,用于检测根集等.
虽然从技术上讲是泄漏,但这两种情况都是有限的,并且在可扩展的循环工作窃取案例中,有一个巨大的性能胜利,以换取队列的内存使用量增加2的有限因子.
如果在程序开头分配了一堆堆,并且在退出时没有释放它,那本身就不是内存泄漏.内存泄漏是指你的程序循环遍历一段代码,并且该代码分配堆然后"丢失跟踪"它而不释放它.
实际上,在退出之前无需调用free()或delete.当进程退出时,操作系统将回收其所有内存(POSIX肯定是这种情况.在其他操作系统上 - 特别是嵌入式操作系统 - YMMV).
在退出时我没有释放内存的唯一警告是,如果你重构你的程序,以便它,例如,成为一个等待输入的服务,无论你的程序做什么,然后循环等待另一个服务调用,然后您编码的内容可能会变成内存泄漏.
这是特定领域的,几乎不值得回答.用你的头脑.
航天飞机操作系统:不,不允许内存泄漏
快速开发概念验证代码:修复所有内存泄漏是浪费时间.
并且存在一系列中间情况.
延迟产品发布以解决除了最糟糕的内存泄漏之外的所有机会成本($$$)通常使任何"草率或不专业"的感觉相形见绌.你的老板付钱让你为他赚钱,而不是为了获得温暖,模糊的感觉.
您必须首先意识到感知内存泄漏与实际内存泄漏之间存在很大差异.非常频繁的分析工具会报告许多红色鲱鱼,并将某些东西标记为泄漏(内存或资源,如手柄等)实际上并非如此.通常这是由于分析工具的架构.例如,某些分析工具会将运行时对象报告为内存泄漏,因为它永远不会看到这些对象被释放.但是重新分配发生在运行时的关闭代码中,分析工具可能无法看到.
话虽如此,仍然有时会出现实际的内存泄漏,这些泄漏要么很难找到,要么很难解决.所以现在问题变成是将它们留在代码中了吗?
理想的答案是"不,永远不会".一个更务实的答案可能是"不,几乎从不".在现实生活中,您经常需要有限的资源和时间来解决无尽的任务列表.当其中一项任务是消除内存泄漏时,收益递减规律经常会发挥作用.您可以在一周内消除98%的应用程序内存泄漏,但剩下的2%可能需要数月.在某些情况下,由于应用程序的架构没有重大的代码重构,甚至可能无法消除某些泄漏.您必须权衡消除剩余2%的成本和收益.
在这种问题中,上下文就是一切.我个人无法忍受泄漏,在我的代码中,如果他们突然出现,我会竭尽全力修复它们,但是修复泄漏并不总是值得的,当人们按小时付款给我时,我偶尔会付钱给我他告诉他们,在我们的代码中修复漏洞并不值得我付费.让我给你举个例子:
我正在试验一个项目,做一些工作并修复很多错误.在我跟踪并完全理解的应用程序初始化期间发生了泄漏.正确修复它需要一天左右的时间来重构一段功能正常的代码.我本可以做一些hacky(比如将值填充到全局并抓住它,我知道它已经不再用于释放了),但这会让下一个不得不触摸代码的人感到更加困惑.
就个人而言,我不会在第一时间编写代码,但我们大多数人都不会总是在原始设计良好的代码库上工作,有时你必须务实地看待这些事情.修复150字节泄漏所花费的时间可以用来进行算法改进,减少兆字节的内存.
最终,我决定泄漏150个字节的应用程序,使用大约一个ram并在专用机器上运行是不值得修复它,所以我写了一条评论说它被泄露,需要改变什么来修复它,以及为什么它在当时不值得.
虽然大多数答案都集中在真正的内存泄漏上(因为它们是编码草率的标志),但这部分问题对我来说更有趣:
如果您分配一些内存并使用它直到应用程序中最后一行代码(例如,全局对象的解构函数),该怎么办?只要内存消耗不会随着时间的推移而增长,当您的应用程序终止时(在Windows,Mac和Linux上),是否可以信任操作系统为您释放内存?如果内存被连续使用直到它被操作系统释放,你甚至会认为这是一个真正的内存泄漏.
如果使用了相关的内存,则无法在程序结束前释放它.免费是由程序退出还是由操作系统完成无关紧要.只要记录了这一点,那么更改不会引入真正的内存泄漏,并且只要图片中没有涉及C++析构函数或C清理函数.可能通过泄漏的FILE
对象显示未关闭的文件,但缺少fclose()也可能导致缓冲区无法刷新.
所以,回到原来的情况,恕我直言就完全没问题了,Valgrind是最强大的泄漏探测器之一,只有在要求的情况下才会对这种泄漏进行处理.在Valgrind上,当你在不事先释放它的情况下覆盖指针时,它会被视为内存泄漏,因为它更有可能再次发生并导致堆无限增长.
然后,没有可以访问的nfreed内存块.人们可以确保在出口处释放所有这些,但这本身就是浪费时间.关键是如果他们之前可以被释放.在任何情况下,降低内存消耗都很有用.