为什么删除[]语法存在于 C + + 中?

每次有人在这里问到关于 delete[]的问题,总会有一个非常普遍的回答: “ C + + 就是这样做的,使用 delete[]”。从一个普通的 C 背景出发,我不明白的是为什么需要一个完全不同的调用。

使用 malloc()/free(),您可以选择获得指向连续内存块的指针,并释放连续内存块。实现中出现了一些问题,并且知道您根据基地址分配的块的大小,以便在必须释放它时使用。

没有函数 free_array()。我已经看到了一些与此相关的其他问题的疯狂理论,比如调用 delete ptr只会释放数组的顶部,而不是整个数组。或者更正确的是,它不是由实现定义的。当然... 如果这是 C + + 的第一个版本,你做了一个奇怪的设计选择,这是有意义的。但是为什么 $PRESENT_YEAR的 C + + 标准没有被超载呢? ? ?

这似乎是 C + + 添加的唯一额外的部分,通过数组和调用析构函数,我认为这可能是它的关键,它实际上是使用一个单独的函数来节省我们一个单独的运行时长度查找,或 nullptr在列表的最后,作为折磨每一个新的 C + + 程序员或程序员谁有一个模糊的一天,忘记了有一个不同的保留字。

有没有人能一劳永逸地澄清一下,除了“标准是这么说的,没有人质疑”之外,还有什么原因?

10443 次浏览

C + + 中的对象通常有析构函数,需要在其生命周期结束时运行。delete[]确保调用数组中每个元素的析构函数。但是这样做 有不明的开销,而 delete没有。这就是为什么有两种形式的 delete 表达式。一个用于数组,它支付开销,另一个用于单个对象,它不支付开销。

为了只有一个版本,实现需要一种机制来跟踪关于每个指针的额外信息。但是 C + + 的基本原则之一就是用户不应该被强迫支付他们并非绝对必须支付的成本。

总是 delete什么你 new总是 delete[]什么你 new[]。但是在现代 C + + 中,newnew[]通常不再使用了。使用 std::make_uniquenew0,new1或其他更具表现力和更安全的替代品。

基本上,mallocfree分配内存,newdelete创建和销毁对象。所以你必须知道物体是什么。

To elaborate on the unspecified overhead François Andrieux's answer mentions, you can see 我对这个问题的回答 in which I examined what does a specific implementation do (Visual C++ 2013, 32-bit). Other implementations may or may not do a similar thing.

如果使用 new[]对象数组和一个非平凡的析构函数,它所做的就是多分配4个字节,并且返回指针向前移动了4个字节,所以当 delete[]想要知道有多少个对象时,它获取指针,向前移动4个字节,并获取该地址的数字,并将其视为存储在那里的对象数。然后对每个对象调用析构函数(通过传递的指针的类型可以知道对象的大小)。然后,为了释放确切的地址,它传递的地址比传递的地址先4个字节。

在此实现中,将用 new[]分配的数组传递给常规 delete会导致调用第一个元素的单个析构函数,然后将错误的地址传递给释放函数,从而破坏堆。别这么做!

没有在其他答案中提到的一些问题(都是好的)是,这个问题的根本原因是从 C 继承而来的数组在 C + + 中从来都不是“一流的”东西。

它们具有基本的 C 语义,没有 C + + 语义,因此支持 C + + 编译器和运行时,这将使您或编译器运行时系统使用指向它们的指针做有用的事情。

In fact, they're so unsupported by C++ that a pointer to an array of things looks just like a pointer to a single thing. That, in particular, would not happen if arrays were proper parts of the language - even as part of a library, like string or vector.

C + + 语言之所以会出现这种问题,是因为它继承了 C 语言的传统。它仍然是 C 语言的一部分——尽管我们现在已经有了用于固定长度数组的 std::array和(一直都有)用于可变长度数组的 std::vector——主要是为了兼容性: 能够从 C + + 调用操作系统 API 和用 C 语言互操作编写的其他语言的库。

而且... ... 因为有大量的书籍、网站和教室在用 C + + 教学法教授数组 非常早,因为 a)能够在早期编写有用/有趣的例子,实际上称为 OS API,当然还因为 b)“这是我们一直在做的方式”。

通常,C + + 编译器及其相关的运行时构建在平台的 C 运行时之上。特别是在这种情况下,C 内存管理器。

C 内存管理器允许您在不知道内存块大小的情况下释放内存块,但是没有标准的方法从运行时获得内存块的大小,也不能保证实际分配的内存块正是您所要求的大小。它很可能更大。

因此,C 内存管理器存储的块大小无法用于启用更高级别的功能。如果高级功能需要关于分配大小的信息,那么它必须自己存储它。(对于带析构函数的类型,C + + delete[]确实需要这样做,以便对每个元素运行析构函数。)

C + + 还有一种“你只为你使用的东西付费”的态度,为每个分配存储一个额外的长度字段(独立于底层分配程序的簿记)不适合这种态度。

由于在 C 和 C + + 中表示未知(在编译时)大小的数组的一般方法是使用指针指向它的第一个元素,编译器无法区分单个对象分配和基于类型系统的数组分配。所以它让程序员来区分。

举个例子也许会有帮助。当您分配一个 C 风格的对象数组时,这些对象可能有自己的析构函数需要被调用。delete操作符不这样做。它适用于容器对象,但不适用于 C 风格的数组。你需要为他们 delete[]

这里有一个例子:

#include <iostream>
#include <stdlib.h>
#include <string>


using std::cerr;
using std::cout;
using std::endl;


class silly_string : private std::string {
public:
silly_string(const char* const s) :
std::string(s) {}
~silly_string() {
cout.flush();
cerr << "Deleting \"" << *this << "\"."
<< endl;
// The destructor of the base class is now implicitly invoked.
}


friend std::ostream& operator<< ( std::ostream&, const silly_string& );
};


std::ostream& operator<< ( std::ostream& out, const silly_string& s )
{
return out << static_cast<const std::string>(s);
}


int main()
{
constexpr size_t nwords = 2;
silly_string *const words = new silly_string[nwords]{
"hello,",
"world!" };


cout << words[0] << ' '
<< words[1] << '\n';


delete[] words;


return EXIT_SUCCESS;
}

该测试程序显式地检测析构函数调用的。这显然是个人为的例子。首先,程序不需要在终止和释放所有资源之前立即释放内存。但它确实展示了发生了什么,以什么顺序发生。

有些编译器(如 clang++)非常聪明,如果在 delete[] words;中遗漏了 [],它会提醒您,但是如果您强制它编译有错误的代码,那么就会导致堆损坏。

封面故事是 delete是必需的 因为 C + + 和 C 的关系

new操作符可以创建几乎任何对象类型的动态分配对象。

但是,由于 C 的传统,指向对象类型的指针在两个抽象之间是不明确的:

  • being the location of a single object, and
  • 是动态数组的基础。

deletedelete[]的情况就是由此而来的。

然而,这听起来并不真实,因为尽管上述观察结果是真实的,但是可以使用单个 delete操作符。逻辑上并不需要两个运算符。

这是非正式的证据。new T操作符调用(单个对象案例)可以隐式地表现得好像它是 new T[1]一样。也就是说,每个 new总是可以分配一个数组。如果没有提到数组语法,那么可以隐式地分配一个 [1]数组。那么,就必须存在一个像今天的 delete[]一样的 delete

为什么没有遵循这个设计?

我认为这可以归结为一般情况: 这是一只被献祭给效率之神的山羊。当使用 new []分配一个数组时,会为元数据分配额外的存储空间,以跟踪元素的数量,这样 delete []就可以知道有多少元素需要迭代以进行销毁。当使用 new分配单个对象时,不需要这样的元数据。对象可以直接在内存中构造,而内存来自底层分配器,不需要任何额外的头。

就运行时成本而言,这是“不要为你不使用的东西付费”的一部分。如果您分配单个对象,您不必为这些对象中的任何表示开销“付费”,以处理指针引用的任何动态对象可能是数组的可能性。但是,您必须负责以数组 new分配对象并随后删除该对象的方式编码该信息。

Delete 是一个操作符,它销毁由新表达式生成的数组和非数组(指针)对象。

It can be used by either using the Delete operator or Delete [ ] operator 一个新的运算符用于动态内存分配,将变量放在堆内存中。 这意味着 Delete 操作符从堆中释放内存。 Pointer to object is not destroyed, value or memory block pointed by the pointer is destroyed. The delete operator has a void return type that does not return a value.