45

可以说我有这样的功能:

int main()
{
    char* str = new char[10];

    for(int i=0;i<5;i++)
    {
        //Do stuff with str
    }

    delete[] str;
    return 0;
}
  1. str如果我要结束程序,为什么我需要删除?如果我只是要退出,我不会在乎那个记忆是否会进入一个充满独角兽的土地,对吧?

  2. 这只是好习惯吗?

  3. 它有更深层次的后果吗?

4

15 回答 15

77

如果实际上您的问题确实是“我有这个微不足道的程序,我可以在它退出之前不释放几个字节吗?” 答案是肯定的,没关系。在任何现代操作系统上都可以。而且程序很简单;这不像你要把它放进心脏起搏器或用这个东西运行丰田凯美瑞的制动系统。如果唯一的客户是你,那么你唯一可能因为马虎而影响的人就是你。

当你开始从这个关于平凡案例的问题的答案中概括到不平凡的案例时,问题就出现了。

因此,让我们就一些非平凡的案例提出两个问题。

我有一个长期运行的服务,它以复杂的方式分配和释放内存,可能涉及多个分配器访问多个堆。在正常模式下关闭我的服务是一个复杂且耗时的过程,它涉及确保外部状态(文件、数据库等)始终处于关闭状态。我是否应该确保在关闭之前释放我分配的每个内存字节?

是的,我会告诉你为什么。长时间运行的服务可能发生的最糟糕的事情之一是它意外泄漏内存。随着时间的推移,即使是微小的泄漏也会导致巨大的泄漏。查找和修复内存泄漏的标准技术是检测分配堆,以便在关闭时记录所有已分配但未被释放的资源。除非您喜欢追踪大量误报并在调试器中花费大量时间,否则请始终释放您的内存,即使严格来说这样做不是必需的。

用户已经预计关闭服务可能需要数十亿纳秒,所以谁在乎您是否对虚拟分配器造成一点额外压力以确保一切都得到清理?这只是您为大型复杂软件支付的价格。而且你并不是一直在关闭服务,所以再一次,谁在乎它是否比它可能慢了几毫秒?

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

当然不是。操作系统会为您解决这个问题。如果您的堆损坏,攻击者可能希望您释放内存作为他们攻击的一部分。每毫秒都很重要。在你将战术核武器扔到建筑物上之前,你为什么还要费心擦亮门把手和擦厨房呢?

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

于 2013-03-19T00:58:01.963 回答
39

Yes it is good practice. You should NEVER assume that your OS will take care of your memory deallocation, if you get into this habit, it will screw you later on.

To answer your question, however, upon exiting from the main, the OS frees all memory held by that process, so that includes any threads that you may have spawned or variables allocated. The OS will take care of freeing up that memory for others to use.

于 2013-03-18T22:27:40.150 回答
24

重要提示:delete释放内存几乎只是一个副作用。它所做的重要事情是破坏对象。对于 RAII 设计,这可能意味着关闭文件、释放操作系统句柄、终止线程或删除临时文件。

当您的进程退出时,其中一些操作将由操作系统自动处理,但不是全部。

在您的示例中,没有理由不调用delete. 但是也没有理由打电话new,所以你可以通过这种方式回避这个问题。

char str[10];

或者,您可以通过使用智能指针来回避删除(以及所涉及的异常安全问题)......

因此,通常您应该始终确保正确管理对象的生命周期。

但这并不总是那么容易:静态初始化顺序失败的解决方法通常意味着您别无选择,只能依靠操作系统为您清理少数单例类型对象。

于 2013-03-18T22:47:31.227 回答
16

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

(并不是我不同意“是”中的原因,我只是认为两种方式都有争论)

于 2013-03-18T22:35:48.663 回答
6

Your Operating System should take care of the memory and clean it up when you exit your program, but it is in general good practice to free up any memory you have reserved. I think personally it is best to get into the correct mindset of doing so, as while you are doing simple programs, you are most likely doing so to learn.

Either way, the only way to guaranteed that the memory is freed up is by doing so yourself.

于 2013-03-18T22:29:52.267 回答
4

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

于 2013-03-19T07:35:58.837 回答
3

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

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

这里的其他答案提供了支持和反对两者的论据,但问题的真正症结在于您的程序做了什么。考虑一个更重要的示例,其中动态分配的类型实例是自定义类,而类析构函数执行一些会产生副作用的操作。在这种情况下,内存泄漏与否的争论是微不足道的,更重要的问题是未能调用delete这样的类实例将导致未定义的行为。

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

程序可以通过重用对象占用的存储空间或通过显式调用具有非平凡析构函数的类类型对象的析构函数来结束任何对象的生命周期。对于具有非平凡析构函数的类类型的对象,在重用或释放对象占用的存储空间之前,程序不需要显式调用析构函数;但是,如果没有显式调用析构函数,或者如果没有使用删除表达式 (5.3.5) 来释放存储,则不应隐式调用析构函数,并且任何依赖于析构函数产生的副作用的程序具有未定义的行为。

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

于 2013-03-19T04:34:09.097 回答
2

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

您永远不知道您的简单示例将如何使用或演变。最好从尽可能干净和清脆的开始。

于 2013-03-27T19:29:57.933 回答
2

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

  • 一些对象有更复杂的析构函数,它们在被删除时不仅仅释放内存。它们可能还有其他副作用,您不想跳过。
  • C++ 标准不保证进程终止时会释放您的内存。(当然在现代操作系统上它会被释放,但如果你在一些奇怪的操作系统上没有这样做,你必须正确地释放你的内存
  • 另一方面,在程序退出时运行析构函数实际上会占用相当多的时间,如果所做的只是释放内存(无论如何都会被释放),那么是的,短路是很有意义的然后立即退出。
于 2013-03-20T07:28:30.163 回答
2

更不用说,如果你要申请一份 C++ 程序员的工作,你很有可能因为缺少删除而无法通过面试。首先 - 程序员通常不喜欢任何泄漏(面试中的人肯定会是其中之一),其次 - 大多数公司(至少我工作过的所有公司)都有“不泄漏”政策。通常,您编写的软件应该运行很长一段时间,在旅途中创建和销毁对象。在这样的环境下,泄漏会导致灾难......

于 2013-03-28T08:48:28.487 回答
2

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

从绝对意义上说,您的应用程序不会泄漏内存;但是,清理分配的内存是一种很好的做法,即使您知道它不会导致真正的泄漏。这个问题是泄漏比没有它们开始更难修复。假设您决定要将 main() 中的功能移动到另一个函数。您最终可能会遇到真正的泄漏。

这也很糟糕,许多开发人员会看到未释放的 'str' 并感到轻微的恶心 :(

于 2013-03-20T08:17:53.010 回答
2

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

  • 概括

    3.它有更深层次的后果吗?

    A:会详细回答。

    2.这只是好习惯吗?

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

    1. str如果我要结束程序, 为什么我需要删除?
      如果我只是要退出,我不会在乎那个记忆是否会进入一个充满独角兽的土地,对吧?

    A:你需要不需要,事实上,说出原因。下面有一些解释。

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

    问:这取决于程序的功能吗?

    A:如果宇宙毁灭是可以接受的,那么不。但是,该程序可能无法按预期正常工作,甚至是一个没有完成它应该完成的程序。您可能要认真思考为什么要构建这样的程序?

    问:是否取决于程序的复杂程度?

    答:没有。见说明。

    Q:这取决于对程序稳定性的期望吗?

    - 答:密切。

    我认为这取决于

    1. 程序的世界是什么?
    2. 该程序完成其工作的期望如何?
    3. 该程序对他人的关心程度如何,以及它所在的宇宙?

      关于术语Universe,请参阅解释。

    总而言之,这取决于关心什么。


  • 解释

    重要提示:如果我们将术语program定义为一个函数,那么它的全域就是application。省略了很多细节;不过,作为一个理解的想法,它已经足够长了。

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

    9RJKM.gif

    但为了了解其中涵盖的范围,我建议采用相反的布局。由于我们只讨论软件,因此下图中省略了硬件层。

    mjLai.jpg

    通过这张图,我们意识到操作系统覆盖了最大的范围,即当前的宇宙,有时我们说环境。你可以想象整个结构由很多圆盘组成,如图所示,要么是圆柱体,要么是圆环体(球很好,但很难想象)。在这里我要提一下,最外面的操作系统实际上是一个unibody,运行时可能是单个或多个不同的实现。

    运行时对操作系统和应用程序负责很重要,但后者更为关键。运行时是应用程序的宇宙,如果它被破坏,所有在其下运行的应用程序都将消失。

    与地球上的人类不同,我们生活在这里,但我们不是由地球组成;如果地球毁灭的时候我们不在,我们仍然会生活在其他合适的环境中。

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

    如上所述,运行时也对操作系统负责。下图中的左圆圈可能是它的样子。

    ScsZs.jpg

    这很像操作系统中的 C 程序。当应用程序和操作系统之间的关系符合这个时,就和上面操作系统中的运行时情况一样。在此图中,操作系统是应用程序的世界。这里的应用程序应该对操作系统负责的原因是操作系统可能没有虚拟化它们的代码,或者被允许崩溃。如果操作系统总是阻止他们这样做,那么无论应用程序做什么,它都是自我负责的。但是想想驱动程序,这是操作系统必须允许崩溃的场景之一,因为这种应用程序被视为操作系统的一部分

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

    那么,您的程序对其他程序的关心程度如何?它对自己的宇宙有多关心?该程序对它完成工作的期望如何?这取决于你关心什么

于 2013-03-20T09:33:22.167 回答
2

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

如果您确实编写了删除所有动态分配内存的代码,那么您将在未来对代码进行校对并让其他人在更大的项目中使用它。

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

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

真相:在频繁执行的代码中省略释放导致
越来越多的泄漏。他们很少被接受。但保留的程序
在程序退出之前分配的最多内存通常在没有
任何干预释放。Malloc 更容易实现,如果
没有免费的。

在大多数情况下,在程序退出之前释放内存是
无意义。无论如何,操作系统都会收回它。自由将触摸和翻页
死物;操作系统不会。

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

  • 如果内存无论如何都会被回收,那么在需要时显式解除分配会有什么危害?

  • 也许如果操作系统“回收”内存的速度比免费的快,那么您会看到性能提高;这种技术不会帮助您处理任何必须长时间运行的程序。

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


如果你养成了这个习惯,谁能说你有一天会不小心将这种方法应用到重要的地方呢?


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


想想你的“A”类必须解构。如果你不打电话
'a' 上的'delete',该析构函数不会被调用。通常,这不会
过程是否结束真的很重要。但是如果析构函数
必须释放例如数据库中的对象?将缓存刷新到日志文件?
将内存缓存写回磁盘?**你看,这不仅仅是“好”
练习”删除对象,在某些情况下需要**。
于 2013-03-26T07:09:58.783 回答
2

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

因为不想偷懒...

如果我只是要退出,我不会在乎那个记忆是否会进入一个充满独角兽的土地,对吧?

不,我也不关心独角兽的土地。阿尔文之地是另一回事,那我们可以把它们的角剪掉,好好利用它们(我听说它是​​一种很好的壮阳药)。

这只是好习惯吗?

这只是一个好习惯。

它有更深层次的后果吗?

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

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

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

int main()
{

    while(1)
    { 
        char* str = new char[10];

        for(int i=0;i<5;i++)
        {
            //Do stuff with str
        }
    }

    delete[] str;
    return 0;
}
于 2013-03-25T16:22:31.800 回答
1

我将讨论一般情况,而不是讨论这个特定示例,因此通常显式调用 delete 以释放内存很重要,因为(在 C++ 的情况下)您可能在析构函数中有一些要执行的代码。就像可能将一些数据写入日志文件或向其他进程发送关闭信号等。如果您让操作系统为您释放内存,您的析构函数中的代码将不会被执行。

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

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

所以在离开之前把你的房间打扫干净!

于 2013-03-30T06:52:52.903 回答