为什么我需要删除?

可以说我有这样的function:

int main() { char* str = new char[10]; for(int i=0;i<5;i++) { //Do stuff with str } delete[] str; return 0; } 
  1. 为什么我需要删除str如果我要结束程序呢? 如果我只是要退出,我不会在乎这个记忆是否到了一个充满独angular兽的地方,对吗?

  2. 这只是一个好习惯吗?

  3. 它有更深的后果吗?

如果实际上你的问题真的是“我有这个微不足道的程序,在我离开之前我没有释放几个字节是可以的吗?” 答案是肯定的,没关系。 在任何现代操作系统上,这将是很好的。 程序是微不足道的; 这不像是你将它放进一个起搏器,或者用这个东西来运行丰田凯美瑞的制动系统。 如果唯一的客户是你,那么你是唯一一个可能会马虎的人。

这个问题就出现在你开始把这个问题的答案推广到非平凡的案例中,

所以让我们来问一些关于一些不平凡的案例的问题。

我有一个长期运行的服务,以复杂的方式分配和释放内存,可能涉及到多个分配器碰撞多堆。 在正常模式下closures服务是一个复杂且耗时的过程,涉及确保外部状态文件,数据库等始终closures。 我应该确保在我closures之前,我分配的每个内存字节都被释放了吗?

是的,我会告诉你为什么。 长期运行的服务可能发生的最糟糕的事情之一就是它意外地泄漏了内存。 即使是微小的泄漏也会随着时间的stream逝而加剧巨大的泄漏。 查找和修复内存泄漏的标准技术是分配堆,以便在closures时logging所有分配的资源而不被释放。 除非你喜欢追查大量的误报,并花费大量的时间在debugging器上,否则即使这样做并不是严格意义上的需要,也应该释放你的内存

用户已经期望closures服务可能需要几十亿纳秒,所以如果你对虚拟分配器造成一点额外的压力,确保所有的东西都被清理了,谁会在乎呢? 这只是你为大型复杂软件付出的代价。 而且这不像你一直在closures服务,那么谁又会在意比它慢几毫秒呢?

我有同样的长期服务。 如果我检测到我的一个内部数据结构已经损坏,我希望“快速失败”。 该程序处于未定义的状态,它可能运行在提升的权限,我将假设,如果我检测到腐败状态,这是因为我的服务正在积极的敌对方的攻击。 最安全的做法是立即closures服务。 我宁愿允许攻击者拒绝为客户提供服务,也不愿让服务停滞不前,并进一步危害我的用户数据。 在这种紧急关机的情况下,我应该确保我分配的每个内存字节都被释放了吗?

当然不是。 操作系统将为您照顾。 如果你的堆是腐败的,攻击者可能希望你释放内存作为其利用的一部分。 每毫秒数。 为什么在打掉战术核弹之前,你还要打磨门把手,擦洗厨房呢?

所以对于这个问题的答案是“在我的程序退出前我应该释放内存吗?” 是“这取决于你的程序做什么”。

是的,这是很好的做法。 你应该永远不要假设你的操作系统会照顾你的内存释放,如果你陷入这个习惯,它会在以后使用。

但是,为了回答你的问题,在从主进程退出时,OS释放该进程所拥有的所有内存,以便包含可能已经产生的任何线程或分配的variables。 操作系统将照顾释放他人使用的内存。

重要提示: delete内存的释放几乎只是一个副作用。 它所做的重要的事情是破坏对象。 使用RAIIdevise,这可能意味着什么,从closures文件,释放OS句柄,终止线程或删除临时文件。

当你的程序退出时,这些操作中的一部分将被操作系统自动处理,但不是全部。

在你的例子中,没有理由不调用delete 。 但是没有理由再打电话给你,所以你可以这样回避这个问题。

 char str[10]; 

或者,您可以通过使用智能指针避开删除(以及涉及的exception安全问题)…

所以,通常你应该始终确保你的对象的生命周期得到妥善pipe理。

但并不总是那么简单: 静态初始化顺序的变通办法经常意味着你别无select,只能依靠操作系统为你清理一些单例types的对象。

相反的答案:不,这是浪费时间。 一个拥有大量分配数据的程序几乎必须触及每一页,以便将所有分配返回到空闲列表。 这会浪费CPU时间,为不感兴趣的数据创build内存压力,甚至可能导致进程从磁盘中交换页面。 只需退出,即可将所有内存释放回操作系统,无需进一步操作。

(不是我不同意“是”的原因,我只是认为有两种说法)

你的操作系统应该关掉内存并在你退出程序的时候进行清理,但是通常情况下你最好先释放你已经保留的内存。 我个人认为,最好是这样做,正如你在做简单的程序时,你最有可能这样去学习。

无论哪种方式,保证记忆被释放的唯一方法就是自己这样做。

newdelete都是保留关键字兄弟。 他们应该通过代码块或通过父对象的生命周期来相互合作。 每当弟弟犯了错误( new ),哥哥会想要清理( delete )它。 然后,母亲(你的计划)会为他们感到高兴和自豪。

我完全同意Eric Lippert的出色build议:

所以对于这个问题的答案是“在我的程序退出前我应该释放内存吗?” 是“这取决于你的程序做什么”。

这里的其他答案也提供了支持和反对的论点,但问题的真正症结在于你的程序 。 考虑一个更加不平凡的例子,其中被dynamic分配的types实例是一个自定义类,而类析构函数执行一些产生副作用的动作。 在这种情况下,内存泄漏与否的争论是微不足道的,更重要的问题是如果在这样的类实例上调用delete会导致未定义的行为。

[basic.life] 3.8对象的生命周期
段落4:

一个程序可以通过重用对象所占用的存储空间来终止任何对象的生命周期,或者通过一个非平凡的析构函数为一个类types的对象显式调用析构函数。 对于具有非平凡析构函数的类types的对象,程序不需要在对象占用的存储被重用或释放之前显式调用析构函数; 然而, 如果没有对析构函数的显式调用,或者没有使用delete-expression(5.3.5)来释放存储,则不应该隐式地调用析构函数,并且任何依赖于析构函数产生的边函数的程序有未定义的行为。

所以你的问题的答案是埃里克说: “取决于你的程序做什么”

这是一个公平的问题,回答时需要考虑以下几点:

  • 有些对象具有更复杂的析构函数,它们在被删除时不会释放内存。 他们可能有其他的副作用,你不想跳过。
  • C ++标准不保证你的内存将在进程终止时被释放。 (当然,在一个现代的操作系统上它将被释放,但是如果你使用的是一些不那么奇怪的操作系统,那么你必须正确地释放你的内存
  • 另一方面,在程序退出时运行析构函数实际上占用了相当多的时间,如果所有的东西都是释放内存(无论如何都会被释放),那么是的,只是短路是很有意义的并立即退出。

大多数操作系统会在退出进程时回收内存。 例外可能包括某些RTOS,旧移动设备等

从绝对意义上说,你的应用程序不会泄漏内存; 然而,清理你分配的内存是一个很好的习惯,即使你知道它不会导致真正的泄漏。 这个问题是泄漏很多,比不让他们开始更难修复。 假设您决定将main()中的function移至另一个function。 你可能最终会有一个真正的泄漏。

这也是不好的美学,许多开发商会看到不自信的'str',感到轻微的恶心:(

为什么我需要删除str,如果我要结束程序呢?

因为你不想偷懒

如果我只是要退出,我不会在乎这个记忆是否到了一个充满独angular兽的地方,对吗?

不,我不关心独angular兽的土地。 阿尔文的土地是一个不同的问题,那么我们可以削减他们的angular,并把它们用好(我听说它是​​一个很好的春药)。

这只是一个好习惯吗?

这是一个很好的做法。

它有更深的后果吗?

其他人必须在你之后清理。 也许你喜欢那样,我多年前从父母的屋檐下搬出来。

在你的代码周围放置一个while(1)循环结构而不删除。 代码复杂性并不重要。 内存泄漏与进程时间有关。

从debugging的angular度来看, 不释放系统资源(文件句柄等)会导致更多的难以发现的错误。 内存泄漏虽然重要,但通常更容易诊断( 为什么我不能写入这个文件? )。 如果您开始使用线程,那么不好的样式将成为更多的问题。

 int main() { while(1) { char* str = new char[10]; for(int i=0;i<5;i++) { //Do stuff with str } } delete[] str; return 0; } 

我还没有看到提到的另一个原因是保持静态和dynamic分析工具(如valgrind或Coverity)的输出更清洁和更安静。 干净的输出与零内存泄漏或零报告的问题意味着,当一个新的popup,更容易检测和修复。

你永远不知道你的简单的例子将如何使用或演变。 最好尽可能地干净清爽。

更何况,如果你要申请一个C ++程序员的工作,那么你很有可能因为缺less删除而不能通过面试。 首先,程序员通常不喜欢任何泄漏(而且面试的人肯定是其中的一员),而第二大的公司(我至less工作过)都有“不泄漏”的政策。 一般来说,你编写的软件应该运行一段时间,创build和销毁移动中的对象。 在这样的环境下泄漏会导致灾难

你有很多专业经验的答案。 我在这里讲的是一个天真的答案,但我认为这是一个事实。

  • 概要

    3.它有更深的后果吗?

    答:会详细回答。

    这只是一个好习惯吗?

    答:这是一个很好的做法。 当您确定不再使用时,释放您检索的资源/内存。

    1. 为什么我需要删除str如果我要结束程序呢?
      如果我只是要退出,我不会在乎这个记忆是否到了一个充满独angular兽的地方,对吗?

    答:实际上, 需要不需要告诉为什么。 有一些解释如下。

    我认为这取决于。 以下是一些假设的问题。 术语程序可能意味着应用程序或function。

    问:这是否取决于程序的function?

    答:如果毁灭的宇宙是可以接受的,那么没有。 但是,该程序可能无法正常工作,甚至是一个程序,它没有完成它应该的。 你可能想认真考虑一下你为什么要创build一个这样的程序?

    问:这是否取决于程序如何复杂?

    答:不可以。

    问:这是否取决于该计划的稳定性?

    A:密切。

    我认为这取决于

    1. 该计划的全部内容是什么?
    2. 该计划对其工作的期望如何?
    3. 程序关心别人多less, 宇宙在哪里?

      关于宇宙这个术语,请看解释。

    总之,这取决于在乎什么。


  • 说明

    重要:如果我们将术语程序定义为函数,那么它的宇宙就是应用程序 。 有许多细节省略; 作为一个理解的理念,这个时间已经够长了。

    我们可能见过这种说明应用软件和系统软件之间关系的图。

    9RJKM.gif

    但是要知道其中涵盖的范围,我会build议一个相反的布局。 由于我们只谈软件,下面的图中省略了硬件层。

    mjLai.jpg

    通过这个图表,我们认识到操作系统覆盖了最大的范围,这是目前的宇宙 ,有时我们说环境 。 你可以想象,整个架构由很多像图那样的盘组成,可以是一个圆柱体或圆环体(一个球很好但很难想象)。 在这里我应该提到,最外层的操作系统实际上是一个单体 ,运行时可能是单个或多个不同的实现。

    运行时对操作系统和应用程序负责是很重要的,但是后者更重要。 运行时是应用程序的全部,如果它被销毁,所有在它下面运行的应用程序都不见了。

    与地球上的人不同,我们住在这里,但我们不是由地球组成的。 如果地球正在毁灭,而我们不在那里,我们仍然会生活在其他适宜的环境中。

    但是,当宇宙被毁灭时,我们不能再存在,因为我们不仅生活在宇宙中,而且也是宇宙的组成部分。

    如上所述,运行时也对OS负责。 下图中的左边圆圈就是它的样子。

    ScsZs.jpg

    这大部分就像操作系统中的C程序。 当应用程序和OS之间的关系与此匹配时,与上述OS中的运行时相同。 在该图中,OS是应用程序的范围。 这里的应用程序的原因应该是操作系统的责任,是操作系统可能不虚拟他们的代码,或允许崩溃。 如果操作系统总是阻止他们这样做,那么无论是什么应用程序,它都是自我负责的。 但是想想驱动程序 ,这是操作系统必须允许崩溃的场景之一,因为这种应用程序被视为OS的一部分

    最后,让我们看看上图中的正确的圆圈 。 在这种情况下,应用程序本身就是宇宙。 有时我们称之为这种应用程序操作系统 。 如果一个操作系统从来不允许加载和运行自定义代码,那么它自己完成所有的事情。 即使它被允许,在其本身被终止之后,内存无处不在,而是硬件 。 所有可能需要的释放,在它被终止之前。

    那么,你的程序关心其他程序有多less? 它关心宇宙多less? 那么这个程序的期望是如何的呢? 这取决于你在乎什么

技术上 ,程序员不应该依靠操作系统来做任何事情。 操作系统不需要以这种方式回收丢失的内存。

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

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

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

真相:在经常执行的代码中省略释放原因
越来越多的泄漏。 他们很less接受。 但保留的程序
大多数分配内存,直到程序退出往往performance更好,没有
任何干预的重新分配。  Malloc实现起来要容易得多
没有免费的。

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

结果:小心使用计数分配的“泄漏检测器”。
有些“泄漏”是好的!
  • 我认为使用malloc / new而不调用free / delete是一个非常糟糕的做法。

  • 如果内存无论如何都会被回收,那么在需要的时候显式释放会有什么危害呢?

  • 也许如果操作系统“快速回收”内存比免费更快,那么你会看到性能提高; 这项技术对于任何必须长时间运行的程序都无能为力。

话虽如此,所以我build议你使用免费/删除。


如果你有这个习惯,谁能说有一天你不会有意外的把这个方法应用到什么地方呢?


一个完成后,应该总是释放资源,无论是文件句柄/内存/互斥。 有了这个习惯,在构build服务器时就不会犯这样的错误。 一些服务器预计将全天候运行。 在这种情况下,任何types的泄漏意味着您的服务器最终将耗尽该资源并以某种方式挂起/崩溃。 一个短的实用程序,雅泄漏不是那么糟糕。 任何服务器,任何泄漏都是死亡。 帮你一个忙。 自己清理。 这是一个好习惯。


想想你的class级“A”必须解构。 如果你不打电话
 'a'上的'删除',那个析构函数将不会被调用。 通常情况下,这不会
无论如何,这个过程终究真的很重要。 但是,如果析构函数呢
必须释放例如数据库中的对象? 刷新caching到日志文件?
写一个内存caching回磁盘?  **你看,这不只是“好”
练习“删除对象,在某些情况下,它是必需的**。 

而不是谈论这个具体的例子,我会谈论一般情况下,所以通常显式调用delete来取消分配内存是很重要的,因为(在C ++的情况下)你可能在析构函数中有一些你想要执行的代码。 就像也许写一些数据到日志文件或发送closures信号到其他进程等。如果你让操作系统释放你的内存给你,你的析构函数中的代码将不会被执行。

另一方面,大多数操作系统将在程序结束时释放内存。 但是这是一个很好的做法,自己释放它,就像我给OSA上面的析构函数的例子不会调用你的析构函数,在某些情况下可能会产生不良行为!

我个人认为,依靠操作系统来释放内存(即使它会这样做)是不好的做法,原因是如果以后你必须将代码与更大的程序集成在一起,你将花费数小时来追踪和修复内存泄漏。

所以在离开前清理你的房间!