为什么我需要删除[]?

时间:2013-03-18 22:24:21

标签: c++ memory memory-management memory-leaks delete-operator

假设我有这样的功能:

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. 它有更深层次的后果吗?

15 个答案:

答案 0 :(得分:75)

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

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

所以,让我们提出两个关于一些非平凡案例的问题。

  

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

是的,我会告诉你原因。长期运行服务可能发生的最糟糕的事情之一就是它会意外泄漏内存。即使很小的泄漏也会随着时间的推移而增加大量泄漏。查找和修复内存泄漏的标准技术是检测分配堆,以便在关闭时记录所有已分配但未释放的资源。除非你喜欢追逐大量误报并在调试器中花费大量时间,否则总是释放你的记忆,即使这样做并非严格说来也是必要的。

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

  

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

当然不是。操作系统将为您处理。如果您的堆已损坏,攻击者可能希望您释放内存作为其利用的一部分。每毫秒都很重要。为什么在你在建筑物上放置战术核武器之前,你还要打扰门把手和拖地厨房吗?

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

答案 1 :(得分:39)

是的,这是一种很好的做法。你永远不应该假设你的操作系统会处理你的内存释放,如果你养成这种习惯,它会在以后搞砸你。

要回答您的问题,在退出main时,操作系统会释放该进程占用的所有内存,因此包括您可能生成的所有线程或分配的变量。操作系统将负责释放内存以供其他人使用。

答案 2 :(得分:24)

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

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

在您的示例中,没有理由不拨打delete。但也没有理由打电话给new,所以你可以这样回避这个问题。

char str[10];

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

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

但这并不总是那么容易:static initialization order fiasco的变通方法通常意味着你别无选择,只能依靠操作系统为你清理一些单独类型的物体。

答案 3 :(得分:16)

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

(不是我不同意“是”中的原因,我认为两种方式都存在争议)

答案 4 :(得分:6)

您的操作系统应该在您退出程序时处理内存并进行清理,但通常的做法是释放您保留的内存。我个人认为最好是这样做正确的心态,因为当你做简单的程序时,你很可能这样做是为了学习。

无论哪种方式,保证释放内存的唯一方法就是自己动手。

答案 5 :(得分:4)

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

答案 6 :(得分:3)

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

  

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

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

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

  

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

所以问题的答案就像Eric说的那样 “取决于你的程序做什么”

答案 7 :(得分:2)

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

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

答案 8 :(得分:2)

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

绝对意义上,您的应用不会泄漏内存;但是,即使您知道它也不会导致真正的泄漏,清理您分配的内存也是一种很好的做法。这个问题是泄漏很多,更难以解决而不是让它们开始。假设您决定要将main()中的功能移动到另一个功能。你最终可能会遇到真正的泄密。

这也是一种糟糕的美学,许多开发人员会看到不一致的'str'并感到轻微的恶心:(

答案 9 :(得分:2)

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

  • 摘要

      

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

    答:会详细回答。

      

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

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

      
        
    1. 如果我要结束程序,为什么还需要删除str?   
        如果我要退出,我不会在乎那些记忆是否到达满是独角兽的土地,对吗?
    2.   

    答:您需要,事实上告诉原因。接下来是一些解释。

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

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

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

      问:问:这取决于程序的复杂程度吗?

    答:不。见解释。

      问:问:这取决于预期程序的稳定性吗?

    A:密切关注。

    我认为这取决于

    1. 该计划的 Universe 是什么?
    2. 该程序对其完成工作的期望如何?
    3. 该程序对其他人的关注程度以及 universe 在哪里?

      关于术语 universe ,请参阅说明。

    4. 总结一下,这取决于关心的内容。


  • 解释

    重要提示:如果我们将术语 program 定义为函数,则其Universe为 application 。省略了许多细节;但是,作为理解的一个想法,它已经足够长了。

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

    9RJKM.gif

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

    mjLai.jpg

    通过此图,我们意识到操作系统涵盖了最大的范围,即当前的 Universe ,有时我们会说环境。您可能会想象整个架构包含很多像图表一样的磁盘,无论是圆柱形还是圆环形(球很好但很难想象)。在这里我要提到的是,最外面的OS实际上是 unibody ,运行时可以是单个或多个不同的实现。

    运行时对操作系统和应用程序负责是非常重要的,但后者更为重要。运行时是应用程序的世界,如果它被销毁,则在其下运行的所有应用程序都将消失。

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

    但是,当宇宙被摧毁时,我们就不复存在了,因为我们不仅生活在宇宙中,而且还包含宇宙。

    如上所述,运行时也对OS负责。下图中的左侧圆圈是它的外观。

    ScsZs.jpg

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

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

    那么,您的计划对其他人的关注程度是多少?它对宇宙的关注程度是多少?以及它完成工作的程序的期望如何?这取决于你关心什么

答案 10 :(得分:2)

  

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

因为你不想懒惰......

  如果我要退出,我不会在乎那些记忆是否会到达满是独角兽的土地,对吗?

不,我也不关心独角兽的土地。 Arwen的土地是另一回事,然后我们可以削减它们的角度并充分利用它们(我已经听说它是一种很好的壮阳药)。

  

这只是好习惯吗?

这是一个很好的做法。

  

它有更深层次的后果吗?

其他人必须在你之后清理。也许你喜欢那样,我从父母身边搬出来了。很多年前屋顶。

在代码周围放置 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;
}

答案 11 :(得分:2)

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

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

来源:Allocation and GC Myths(PostScript提醒!)

Allocation Myth 4: Non-garbage-collected programs should always
deallocate all memory they allocate.

The Truth: Omitted deallocations in frequently executed code cause
growing leaks. They are rarely acceptable. but Programs that retain
most allocated memory until program exit often perform better without
any intervening deallocation. Malloc is much easier to implement if
there is no free.

In most cases, deallocating memory just before program exit is
pointless. The OS will reclaim it anyway. Free will touch and page in
the dead objects; the OS won't.

Consequence: Be careful with "leak detectors" that count allocations.
Some "leaks" are good!
  • 我认为使用malloc / new而不调用free / delete是一种非常糟糕的做法。

  • 如果内存无论如何都会被收回,那么在你需要的时候明确解除分配会有什么危害呢?

  • 如果操作系统“回收”内存的速度比免费快,那么你会看到性能提升;对于任何必须长时间保持运行的程序,此技术无法帮助您。

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


如果你养成了这种习惯,谁会说你有一天不会意外地在某个地方应用这种方法呢?


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


Think about your class 'A' having to deconstruct. If you don't call
'delete' on 'a', that destructor won't get called. Usually, that won't
really matter if the process ends anyway. But what if the destructor
has to release e.g. objects in a database? Flush a cache to a logfile?
Write a memory cache back to disk? **You see, it's not just 'good
practice' to delete objects, in some situations it is required**. 

答案 12 :(得分:2)

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

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

答案 13 :(得分:2)

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

答案 14 :(得分:1)

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

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

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

离开之前要打扫你的房间!