为什么我需要删除[]?

use*_*498 45 c++ memory memory-leaks memory-management delete-operator

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

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

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

    delete[] str;
    return 0;
}
Run Code Online (Sandbox Code Playgroud)
  1. str如果我要结束程序,为什么还需要删除?如果我要退出,我不在乎那个记忆是否会到达满是独角兽的土地,对吧?

  2. 这只是好习惯吗?

  3. 它有更深的后果吗?

Eri*_*ert 77

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

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

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

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

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

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

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

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

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

  • @KenKin:我曾经去过法国亚眠的公元1220年.我用凿子问了一个人他做了什么."我是一名石匠;我把这些块石头切开,使它们光滑,并将它们组装成柱子".我问一个人伪造他做了什么."我是一个玻璃吹制机.我制作大块扁平玻璃,并将它们切成窗户形状".我问了一个看到他做了什么的人."我是一个木匠;我是开门的".我问了一位带扫帚的老妇人,把她所做的石头,玻璃和木屑都扫了一遍."**我建造了大教堂.**" (4认同)
  • @GeReV:我从来没有写过任何有关堆腐败漏洞的文章,但互联网上有很多关于它的文章. (2认同)

75i*_*ist 39

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

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

  • @JakobS:这取决于你的定义.我会说聪明的指针有效地管理你的记忆...... (14认同)
  • @Roddy任何运行完整*nix内核等的东西显然都会这样做.当你进入嵌入式的东西时,你甚至没有操作系统,只有一个带有一些任务控件的最小内核,你可能没有那么幸运.这些爬行者通常没有名称,并且在某些特定设备内部进行开发/剥离/修补. (2认同)

Rod*_*ddy 24

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

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

在你的例子中,没有理由不打电话delete.但也没有理由打电话new,所以你可以这样回避问题.

char str[10];
Run Code Online (Sandbox Code Playgroud)

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

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

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


Ben*_*son 16

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

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

  • 这就是我认为不释放内存的主要原因.如果你有一个巨大的已分配内存块,你不希望程序在最后等待所有内存被释放.重要的是要完全确定正在运行的程序的OS将在您依赖该细节之前适当地处理它. (3认同)
  • 通常,释放内存要比分配内存快得多,而且我从未见过堆实现会随着块大小而增加free的性能。如果您谈论的是成千上万个小块,那是另一个问题:但是,问题就出在设计的残缺之一上。仍然没有借口进入懒惰的心态,不去删除对象。 (2认同)
  • @Roddy:具有许多复杂分配的大型程序将以随机顺序触摸其内存的每个页面(重复).这肯定会对系统上的其他应用程序产生负面影响,尤其是在部分现有应用程序被换出时. (2认同)
  • @Roddy:你的应用程序必须经受意外终止,因为系统崩溃,不耐烦的重启脚本,未处理的异常等等.RAII很不错,但我不依赖它来确保我的40瓦激光器在控制程序退出时不会触发!就像我说的那样,我只是提供反驳. (2认同)

ean*_*son 6

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

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