RError.com

RError.com Logo RError.com Logo

RError.com Navigation

  • 主页

Mobile menu

Close
  • 主页
  • 系统&网络
    • 热门问题
    • 最新问题
    • 标签
  • Ubuntu
    • 热门问题
    • 最新问题
    • 标签
  • 帮助
主页 / 问题 / 934677
Accepted
pepsicoca1
pepsicoca1
Asked:2020-01-21 16:42:01 +0000 UTC2020-01-21 16:42:01 +0000 UTC 2020-01-21 16:42:01 +0000 UTC

C++ 中 delete[] 运算符的冗余

  • 772

在这里,和往常一样,在错误的时间,我想到了以下问题。从堆分配内存时,很明显堆必须存储有关所请求内存区域大小和所请求元素数量的信息。那么为什么会有单独的运算符deleteanddelete[]呢?毕竟,无论我们请求的是向量还是单个元素,堆都包含有关请求区域大小和请求元素数量的信息。不可能在请求堆中的一个元素时,没有存储请求一个元素的信息。如果是这种情况,那么操作员delete可以很好地确定(根据堆中包含的服务信息)是请求了一个数组还是请求了一个元素。并且,相应地,将一个元素或一个向量的内存返回到堆中。事实证明,运营商delete[]多余的。

UPD1:

在当前的方法中,C++ 更经济:它仅将其附加信息存储在具有非平凡析构函数的对象数组中。

所以你的意思是堆有几种格式?请求的单个元素的一种格式,请求的向量的另一种格式,具有非平凡分配器/析构函数的请求的单个元素的第三种格式,具有非平凡分配器/析构函数的请求向量的第四种格式?好吧,这取决于编译器和堆的开发人员,当然,他们如何看待自己的任务以使其尽可能高效。但乍一看,拥有许多堆格式并不比存储所有信息并在运行时弄清楚现在到底有什么更有效。此外,您仍然需要了解许多堆格式的运行时和方法。

UPD2:

是的,非平凡的分配器还存储有关请求元素的数量和堆中一个元素的大小的信息。有了这些信息,一个重要的析构函数就可以弄清楚它到底需要删除什么。同样,在这种情况下,不需要 delete[ 运算符,删除运算符就足够了。

UPD3:

现在让我们先不管非平凡的分配器。现在考虑标准分配器,特别是因为两种情况下的问题是相同的。

因此,有一个堆,并且有 new 和 new[] 运算符。两个算子都需要在堆的服务信息中输入一个对象的大小和请求中的对象数量数据。因此,只需要一个删除内存返回操作符,因为根据服务信息,运行时可以而且应该准确计算出请求了多少对象。因此,delete[] 运算符是多余的。

现在考虑非标准(自定义)分配器。同理,用户 new 和 new[] 需要将请求中的一个对象的大小和 NUMBER of OBJECTS 的数据输入到堆的服务信息中。此外,用户定义的 new 和 new[] 需要在堆开销中包含指向用户定义的析构函数的指针。同样,在这种情况下,只需要一个删除内存返回运算符,因为根据服务信息,运行时可以而且应该准确地计算出请求了多少对象。因此,delete[] 运算符是多余的。

c++
  • 4 4 个回答
  • 10 Views

4 个回答

  • Voted
  1. Best Answer
    AnT stands with Russia
    2020-01-21T17:03:31Z2020-01-21T17:03:31Z

    首先,即使有关请求块大小的信息(以字节delete为单位)存储在堆中,也只有在使用“常规”分配器时才能知道存储此信息的方法。但是 C++ 中的原始内存分配过程是用户可重载的。一旦内存分配传递给自定义分配器,delete它就无法再确定块大小。

    其次,即使块的大小(以字节为单位)已知,仍然不可能从这个大小中明确地恢复数组中元素的确切数量以调用析构函数的确切数量。块大小可以大于存储元素所需的确切值。

    第三,不清楚您在说什么“请求的项目数量”。请求元素的数量正好存储new []并减去delete []。事实上,为了delete []将它与delete. 在此处查看详细信息:如果 C/C++ 不知道数组的大小,它如何知道要释放多少内存?

    理论上,你可以做一个“聪明delete”的人,它总是什么都懂。但这会导致无条件地需要在所有内存块中存储额外的信息。在当前的方法中,C++ 更经济:它仅将其附加信息存储在具有非平凡析构函数的对象数组中。

    事实上,几乎这样的“聪明delete”你已经拥有了。没有人在任何地方禁止你,只是无条件地使用new[]/delete[]和忘记存在new/delete。也就是说,单个对象可以简单地分配为大小为 1 的数组。但这会更加浪费(并且不支持多态删除)。


    回复您的 UPD1:

    在典型的实现中,C++ 堆中的块实际上具有三种格式:对于单个对象 ( new)、对于具有普通析构函数的数组 ( new[]) 以及对于具有非普通析构函数的数组 ( new[])。

    同时,从内部结构的角度来看,前两种格式可以认为是相同的,因为 它们只是“内存块”。但这里的事实干扰了在 C++ 中分配/释放“原始”内存的机制被用户重载的事实:不管 fornew/delete和 for new[]/delete[]。因此,这些是单独的格式。


    回答您的 UPD3:

    不知道你哪来的“要求两个算子都在堆的服务信息中输入一个对象的大小和请求中的对象数量的数据”的想法。这绝对不是真的。

    再一次:它根本new不存储此类信息。对于具有简单析构函数的类型,new[]它也不存储有关对象数量或大小的任何信息。这些格式只是简单地通过一个普通的分配内存,通过一个普通malloc的释放它free,并没有在这个内存块中保存任何额外的内部信息。从 C++ 的角度来看,内存的需求量与存储用户数据的需求量完全相同。

    它仅new[]与具有重要析构函数的数组不同。只有它在块中存储有关数组中元素的确切数量的服务信息(因此分配的内存比用户数据所需的多一点)。

    根本不会存储有关此类块中一个元素大小的信息 - 这根本没有必要。

    我只谈论标准分配器。自定义分配器与它无关。

    • 15
  2. Герман Борисов
    2020-01-21T16:59:33Z2020-01-21T16:59:33Z

    您将接口和实现细节混合在一起。而且,您很可能忘记了 C++ 不仅用于 x86 兼容系统。

    语言(接口)对堆一无所知。这就是实现。New 可以很好地分配内存,例如,从一个slab 分配器,而 New[] 将使用堆。

    此外,单片机的内存限制很严重,对 New/Delete 和 New[]/Delete[] 做同样的实现简直是浪费,因为 第二对不仅必须知道内存区域的大小,还必须知道其中实际元素的数量。

    • 10
  3. talex
    2020-01-21T17:01:03Z2020-01-21T17:01:03Z

    事实是,虽然运行时知道大小(在一般情况下不是这样,它可能不会存储大小),但它不知道其中包含什么。理论上,您可以尝试猜测,但猜测算法必须在规范中进行描述。这是不必要的并发症。

    简短的回答:保持规范简单并使其更加灵活。

    • 6
  4. MGNeo
    2020-01-21T19:50:51Z2020-01-21T19:50:51Z

    据我所知,和的存在new / delete是new[] / delete[]由格言证明的:не платим за то, что не используем.

    这种方法可以节省内存和不必要的检查周期。

    当然,内存管理器会保存有关每个块大小的信息,但是仅内存块的大小并不足以通过为每个对象调用其析构函数来正确处理对象数组的删除。

    在C这种情况下不存在分区问题,因为它free()只是在不调用析构函数的情况下释放内存。

    如果我们尝试仅使用有关内存块大小的信息来删除对象数组C++,那么我们将无法做到这一点。我们缺乏信息。

    因此,它new[]为每个对象数组启动至少两个计数器。这些计数器的内容取决于编译器的实现,但通常计数器存储размер блока памяти和тип объекта。知道了对象的类型,我们就有了关于对象大小及其析构函数的信息。

    只有在这种情况下,才有足够的信息来正确调用每个数组对象的析构函数,然后释放内存块。

    如果只有delete一个可以同时处理单个对象和对象数组,那么在处理单个对象时,内存和处理器周期将被过度使用以进行不必要的检查。

    支持这一理论的是以下代码:

    while (1)
    {
        float *arr = new float[1000];
        delete arr;
    
        arr = new float;
        delete[] arr;
    }
    

    大多数情况不会导致跌落或泄漏。是的,它是UB,但我见过很多这样的代码是故意编写并工作多年的。

    由于float它没有析构函数,因此没有混淆 delete问题,而且delete[]大多数情况下没有问题。

    当然,里面的一切都要复杂得多,但所发生的事情的一般本质是这样的。

    • 6

相关问题

Sidebar

Stats

  • 问题 10021
  • Answers 30001
  • 最佳答案 8000
  • 用户 6900
  • 常问
  • 回答
  • Marko Smith

    根据浏览器窗口的大小调整背景图案的大小

    • 2 个回答
  • Marko Smith

    理解for循环的执行逻辑

    • 1 个回答
  • Marko Smith

    复制动态数组时出错(C++)

    • 1 个回答
  • Marko Smith

    Or and If,elif,else 构造[重复]

    • 1 个回答
  • Marko Smith

    如何构建支持 x64 的 APK

    • 1 个回答
  • Marko Smith

    如何使按钮的输入宽度?

    • 2 个回答
  • Marko Smith

    如何显示对象变量的名称?

    • 3 个回答
  • Marko Smith

    如何循环一个函数?

    • 1 个回答
  • Marko Smith

    LOWORD 宏有什么作用?

    • 2 个回答
  • Marko Smith

    从字符串的开头删除直到并包括一个字符

    • 2 个回答
  • Martin Hope
    Alexandr_TT 2020年新年大赛! 2020-12-20 18:20:21 +0000 UTC
  • Martin Hope
    Alexandr_TT 圣诞树动画 2020-12-23 00:38:08 +0000 UTC
  • Martin Hope
    Air 究竟是什么标识了网站访问者? 2020-11-03 15:49:20 +0000 UTC
  • Martin Hope
    Qwertiy 号码显示 9223372036854775807 2020-07-11 18:16:49 +0000 UTC
  • Martin Hope
    user216109 如何为黑客设下陷阱,或充分击退攻击? 2020-05-10 02:22:52 +0000 UTC
  • Martin Hope
    Qwertiy 并变成3个无穷大 2020-11-06 07:15:57 +0000 UTC
  • Martin Hope
    koks_rs 什么是样板代码? 2020-10-27 15:43:19 +0000 UTC
  • Martin Hope
    Sirop4ik 向 git 提交发布的正确方法是什么? 2020-10-05 00:02:00 +0000 UTC
  • Martin Hope
    faoxis 为什么在这么多示例中函数都称为 foo? 2020-08-15 04:42:49 +0000 UTC
  • Martin Hope
    Pavel Mayorov 如何从事件或回调函数中返回值?或者至少等他们完成。 2020-08-11 16:49:28 +0000 UTC

热门标签

javascript python java php c# c++ html android jquery mysql

Explore

  • 主页
  • 问题
    • 热门问题
    • 最新问题
  • 标签
  • 帮助

Footer

RError.com

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

帮助

© 2023 RError.com All Rights Reserve   沪ICP备12040472号-5