繁体   English   中英

为什么我需要删除[]?

[英]Why do I need to delete[]?

让我们说我有这样的功能:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

在您的示例中,没有理由不调用delete 但也没有理由要求new ,所以你可以这样回避问题。

char str[10];

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  • 摘要

    3.它有更深的后果吗?

    答:会详细回答。

    2.难道只是好的做法呢?

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

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

    答:你需要或者不需要,事实上, 告诉为什么。 下面有一些解释。

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

    问:这取决于该计划的作用吗?

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

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

    答:不可以。见解释。

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

    答:关系密切。

    我认为这取决于

    1. 什么是程序的宇宙
    2. 该计划的期望是如何完成其​​工作的?
    3. 该计划对其他人及其所处的世界有多少关注?

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

    总结一下,这取决于在乎什么。


  • 说明

    重要提示:如果我们将术语“ 程序”定义为函数,那么它的Universe就是应用程序 省略了许多细节; 作为理解的一个想法,它已经足够长了。

    我们可能已经看到过这种图表,它说明了应用软件和系统软件之间的关系。

    9RJKM.gif

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

    mjLai.jpg

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

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

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

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

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

    ScsZs.jpg

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

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

    那么,你的程序对其他程序的关注程度是多少? 它对宇宙的关注程度是多少? 该项目的期望是如何完成其​​工作的? 这取决于你在乎什么

如果我要结束程序,为什么还需要删除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;
}

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

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

来源: 分配和GC神话 (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**.

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

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

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

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

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

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

所以在离开之前打扫房间!

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM