为什么有一个特殊的new和删除数组?
使用delete
而不是delete[]
什么问题?
在分配和释放数组的过程中是否有一些特殊的事情发生?
为什么它与malloc
和free不同?
使用new[]
创建的对象必须使用delete[]
。 在数组上使用delete
是未定义的。
使用malloc和free,你有一个更简单的情况。 只有一个函数可以释放您分配的数据,也没有被调用析构函数的概念。 混淆只是因为delete[]
和delete看起来很相似。 实际上它们是两个完全不同的function。
使用delete不会调用正确的函数来删除内存。 它应该调用delete[](void*)
,而是调用delete(void*)
。 因此,您不能依赖使用delete
来分配new[]
内存
请参阅此C ++ FAQ
[16.13]删除某些内置类型(char,int等)的数组时,是否可以删除
[]
?没有!
有时程序员认为
delete[] p
中的delete[] p
只存在,因此编译器会为数组中的所有元素调用相应的析构函数。 由于这种推理,他们假设某些内置类型的数组(如char
或int
可以在没有[]
情况下delete
d。 例如,他们假设以下是有效的代码:void userCode(int n) { char* p = new char[n]; ... delete p; // ← ERROR! Should be delete[] p ! }
但上面的代码是错误的,它可能会在运行时导致灾难。 特别是,调用
delete p
的代码是operator delete(void*)
,但是为delete[] p
调用的代码是operator delete[](void*)
。 后者的默认行为是调用前者,但允许用户用不同的行为替换后者(在这种情况下,他们通常也会替换operatornew[](size_t)
的相应新代码)。 如果他们替换了delete[]
代码,因此它与删除代码不兼容,并且你调用了错误代码(即,如果你说delete p
而不是delete[] p
),你可能会在运行时遇到灾难。
为什么delete[]
首先存在?
无论你做x还是y:
char * x = new char[100]; char * y = new char;
两者都存储在char *
类型的变量中。
我认为delete
和delete[]
的原因伴随着一长串有利于C ++效率的决策。 这样就没有强制价格来查找正常删除操作需要删除多少。
有两个new
和new[]
似乎只有delete
和delete[]
无论如何对称性是合乎逻辑的。
区别在于delete
只会删除整个内存范围,但只会为1个对象调用析构函数。 delete[]
将删除内存并为每个对象调用析构函数。 如果不对数组使用delete[]
,那么在将资源泄漏引入应用程序之前只是时间问题。
编辑更新
根据该标准,未定义将用new[]
分配的对象传递给delete
。 可能的行为是它将按照我的描述行事。
Stroustrup在第10.3到10.5.1节中讨论了“C ++的设计和演变”中单独的new
/ new[]
和delete/
delete []`运算符的原因:
- 10.3数组分配 – 讨论了他们想要一种方法来允许使用与分配单个对象分开的单独方案来分配对象数组(即,从单独的存储分配数组)。 添加
new
和delete
的数组版本是一个解决方案; - 10.5.1解除分配数组 – 讨论如何仅使用单个
delete
运算符解除分配数组的问题是,除了指针之外还需要更多信息以确定指针是指向数组的第一个元素还是仅仅指向数组指向单个对象。delete[]
运算符用于处理数组,而不是“使分配和释放单个对象的常见情况复杂化”。 这符合一般的C ++设计哲学“不为你不使用的东西买单”。
这个决定是否是一个错误是有争议的 – 无论哪种方式有充分的论据,但我们拥有我们所拥有的。
这个要求的原因是历史性的,因为new type
和new type [size]
返回需要以不同方式清理的不同内容。
考虑这段代码
Foo* oneEntry = new Foo; Foo* tenEntries = new Foo[10];
这两个都返回一个Foo *指针,区别在于第二个调用将导致Foo构造函数被调用10x,并且内存大约是10倍。
所以现在你要释放你的对象。
对于单个对象,您可以调用delete – 例如delete oneEntry
。 这会调用对象析构函数并释放内存。
但问题在于 – oneEntry和tenEntries都只是Foo指针。 编译器不知道它们是指向一个,十个还是一千个元素。
当您使用delete []
的特殊语法时。 这告诉编译器“这是一个对象数组,计算出数,然后将它们全部破坏”。
真正发生的是,对于new type [size]
,编译器在其他地方秘密存储“大小”。 当你调用delete []时,它知道这个秘密值存在,因此它可以找出该内存块中有多少个对象并销毁它们。
您可以问的问题是“为什么编译器不会始终存储大小?”
这是一个很好的问题,它可以追溯到C ++的早期阶段。 希望对于内置类型(char,int,float等),以下内容对C ++有效;
int* ptr = new int; free(ptr); int* ptr = (int*)malloc(sizeof(int) * someSize); delete ptr;
这背后的原因是期望人们会提供返回动态分配内存的库,这些库的用户无法知道是否使用free / delete。
这种对兼容性的要求意味着数组的大小不能作为数组本身的一部分存储,而是必须保存在其他地方。 由于这种开销(并且记得,这是在80年代早期),因此决定只保留数组而不是单个元素。 因此,数组需要一种特殊的删除语法来查找此值。
malloc / free没有这个问题的原因是它们只是处理内存块而不必担心调用构造函数/析构函数。
关于标题中的“为什么”:C ++的设计目标之一是不存在任何隐藏成本。 C ++也是在内存的每个字节仍然比现在更重要的时候开发的。 语言设计者也喜欢正交性:如果你用new[]
(而不是new
)分配内存,你应该用delete[]
释放它。
我不认为有任何技术原因, new[]
不能在内存块的标题中粘贴“我是一个数组”标志以便delete
(不再需要delete[]
)以便稍后查看。
new
和delete
与malloc
不同, malloc
free
和free
只分配和释放内存; 他们不打电话给ctors或dtors。
当您使用new[]
分配数组时,实际上是在告诉C ++数组的大小。 当您使用malloc
,您会告诉它分配了多少内存。 在前一种情况下,基于数组大小的释放是没有意义的。 在这种情况下,确实如此。 但由于数组指针与单个对象之间没有区别,因此需要单独的函数。