代码之家  ›  专栏  ›  技术社区  ›  Ashish

为什么在delete(delete[])中使用[]来释放动态分配的数组?

  •  9
  • Ashish  · 技术社区  · 15 年前

    我知道什么时候 delete [] 将导致所有数组元素的破坏,然后释放内存。

    我最初认为编译器希望它只为数组中的所有元素调用析构函数,但我也有一个相反的参数:

    堆内存分配器必须知道分配的字节大小并使用 sizeof(Type) 它可以找到元素的no,并为数组调用适当的析构函数no,以防止资源泄漏。

    所以我的假设是否正确,请澄清我对它的怀疑。

    所以我不知道 [] 在里面 删除[ ] ?

    5 回复  |  直到 9 年前
        1
  •  35
  •   seaotternerd    9 年前

    Scott Meyers在他有效的C++书中说:项目5:在新的和删除的相应使用中使用相同的形式。

    删除的大问题是:被删除的内存中有多少对象?这个问题的答案决定了必须调用多少析构函数。

    被删除的指针是指向单个对象还是指向对象数组?删除的唯一方法就是告诉它。如果在使用delete时不使用括号,则delete假定指向单个对象。

    此外,内存分配器可能会分配存储对象所需的更多空间,在这种情况下,将返回的内存块大小除以每个对象的大小是行不通的。

    根据平台的不同, _msize (Windows) malloc_usable_size (Linux)或 malloc_size (OSX)函数将告诉您分配的块的实际长度。在设计成长容器时可以利用这些信息。

    它不起作用的另一个原因是 Foo* foo = new Foo[10] 电话 operator new[] 分配内存。然后 delete [] foo; 电话 operator delete[] 释放内存。因为这些运算符可能超载, 你必须遵守公约否则 delete foo; 电话 operator delete 其实现可能与 operator delete [] . 这是一个语义问题,而不仅仅是跟踪分配对象的数量,以便稍后发出正确数量的析构函数调用。

    参见:

    [16.14] After p = new Fred[n], how does the compiler know there are n objects to be destructed during delete[] p?

    简短的回答:魔法。

    答案很长:运行时系统将对象数量n存储在某个地方,如果您只知道指针p,就可以在那里检索到它。有两种流行的技术可以做到这一点。这两种技术都被商业级的编译器使用,两者都有权衡,两者都不完美。这些技术是:


    编辑:在阅读了《安德烈评论》之后,我翻阅了Stroustrup的《C++设计与演化》一书,摘录如下:

    如何确保正确删除数组?特别是,我们如何确保对数组的所有元素调用析构函数?

    处理单个对象和数组时不需要纯删除。这避免了分配和解除分配单个对象的常见情况复杂化。它还避免将数组释放所需的信息拖累到单个对象。

    delete[]的中间版本要求程序员指定数组元素的数目。

    ……

    事实证明,这太容易出错,因此,跟踪元素数量的负担反而放在了实现上。

    正如@marcus所提到的,理性可能是“你不为你不使用的东西付费”。


    编辑2:

    在《C++程序设计语言,第三版》,第10版中,Bjarne Stroustrup写道:

    数组和单个对象的具体分配方式取决于实现。因此,不同的实现对delete和delete[]运算符的错误使用会有不同的反应。在像前一种简单而无趣的情况下,编译器可以检测到问题,但通常情况下,运行时会发生一些令人讨厌的事情。

    数组的特殊销毁运算符delete[]在逻辑上不是必需的。然而,假设自由存储的实现被要求保存足够的信息,以便每个对象都知道它是单个的还是一个数组。用户可以免除负担,但这种义务会对某些C++实现施加显著的时间和空间开销。

        2
  •  10
  •   AnT stands with Russia    12 年前

    决定分开的主要原因 delete delete[] 这两个实体并不像第一眼看到的那样相似。对于一个幼稚的观察者来说,它们看起来几乎是一样的:只是销毁和释放,处理的对象的潜在数量只有一个差异。实际上,两者之间的差异更为显著。

    两者最重要的区别是 删除 可能执行 多态性缺失 对象的类型,即所讨论对象的静态类型可能不同于其动态类型。 删除[ ] 另一方面,必须严格处理数组的非多态删除。因此,在内部,这两个实体实现了显著不同的逻辑,并且两者之间不相交。由于多态性缺失的可能性, 删除 甚至与 删除[ ] 在一个包含1个元素的数组上,正如一个天真的观察者最初可能错误地假定的那样。

    当然,与其他答案中的奇怪说法相反,完全有可能取代 删除 删除[ ] 只使用一个在早期阶段分支的构造,即它将使用存储在 new / new[] ,然后跳到适当的功能,相当于 删除 删除[ ] . 然而,这将是一个相当糟糕的设计决策,因为这两者的功能又一次太不同了。将两者都强制为一个结构,类似于创建一个具有释放功能的瑞士军刀。此外,为了能够区分一个数组和一个非数组,我们必须引入一个额外的家庭信息,即使是在一个对象内存分配中,也要用plain 新的 . 这很容易在单个对象分配中导致显著的内存开销。

    但是,这里的主要原因是 删除 删除[ ] . 这些语言实体只有明显的、肤浅的相似性,这种相似性只存在于幼稚规范的层次上(“析构函数和自由内存”),但是一旦人们详细了解了这些实体真正需要做的事情,就会意识到它们太不同了,无法合并为一个实体。

    另外,这是关于 sizeof(type) 你提出了这个问题。因为 删除 你不知道输入的是什么 删除 这就是为什么你得不到 尺寸(类型) .这个想法还有很多问题,但这个已经足够解释为什么它不能飞了。

        3
  •  3
  •   sharptooth    15 年前

    堆本身知道分配的块的大小-您只需要地址。看起来free()可以工作-您只需传递地址,它就可以释放内存。

    两者之间的区别 delete ( delete[] )和 free() 前两个函数首先调用析构函数,然后释放内存(可能使用 自由() )问题是 删除[ ] 也只有一个参数——地址,并且只有那个地址,它需要知道运行析构函数的对象的数量。所以 new[] 使用SOM实现定义的方法在某个地方写入元素数-通常它会在数组前面加上元素数。现在,delete[]将依赖于特定于实现的数据来运行析构函数,然后释放内存(同样,只使用块地址)。

        4
  •  1
  •   justin    15 年前

    delete[] 只是调用不同的实现(函数);

    没有理由分配程序 不能 跟踪它(事实上,写自己的就足够容易了)。

    我不知道他们没有管理它的原因,也不知道实现的历史,如果我猜的话:其中很多“好吧,为什么这不是稍微简单一点?”问题(在C++中)归结为一个或多个:

    1. 与C兼容
    2. 性能

    在这种情况下,性能。使用 delete VS 删除[ ] 很简单,我相信它可以从程序员那里抽象出来,并且相当快(一般使用)。 删除[ ] 只需要一些额外的函数调用和操作(省略析构函数调用),但这是 每次要删除的呼叫 ,这是不必要的,因为程序员通常知道他/她正在处理的类型(如果不知道,可能有一个更大的问题在手)。所以它只是避免通过分配器进行调用。此外,这些单个分配可能不需要分配器进行如此详细的跟踪;将每个分配视为一个数组将需要额外的条目来计算琐碎的分配,因此它是多个级别的简单分配器实现简化,这对许多人来说实际上很重要,因为它是一个非常低级别的域。

        5
  •  -1
  •   Pavel Radzivilovsky    15 年前

    这就更复杂了。

    关键字和使用它来删除数组的约定是为了方便实现而发明的,有些实现确实使用它(不过我不知道是哪个)。MS VC++没有)。

    方便之处在于:

    在所有其他情况下,您知道通过其他方式释放的确切大小。删除单个对象时,可以从编译时sizeof()获取大小。当您通过基指针删除一个多态对象,并且您有一个虚拟析构函数时,您可以在vtbl中将该大小作为一个单独的条目。如果删除一个数组,除非您单独跟踪它,否则您如何知道要释放的内存大小?

    特殊的语法只允许对数组跟踪这样的大小——例如,将数组放在返回给用户的地址之前。这将占用额外的资源,而非数组不需要。