2008-10-31 77 views
23

这是一个唠叨我一段时间的问题。我一直认为应该设计C++,以便即使使用“new []”运算符,“删除”运算符(无括号)也能工作。为什么我们甚至需要“delete []”运算符?

在我看来,写这个:

int* p = new int; 

应相当于分配1个元素的数组:

int* p = new int[1]; 

如果这是真的,“删除”操作者总是可以删除数组,而我们不需要“delete []”运算符。

为什么在C++中引入“delete []”运算符有什么原因吗?我能想到的唯一原因是分配数组的内存占用量很小(您必须将数组大小存储在某处),因此区分“delete”和“delete []”是一个小内存优化。

回答

32

这样就可以调用各个元素的析构函数。是的,对于POD数组,没有什么区别,但在C++中,可以使用非平凡的析构函数来创建对象数组。

现在,你的问题是,为什么不把newdelete的行为很像0​​和delete[],摆脱new[]delete[]?我会回到Stroustrup的“设计和演变”一书中,他说如果你不使用C++特性,你不应该为它们付费(至少在运行时)。现在,newdelete的行为将与mallocfree一样有效。如果delete具有delete[]的含义,那么在运行时会有一些额外的开销(正如James Curran指出的那样)。

+4

实际上,当你使用new int [1]时,它在第一个数据之前存储在数组的头部,它的大小。因此,使用delete而不是delete []不会释放该部分内存。 – Rodrigo 2009-04-24 13:35:03

2

delete []确保每个成员的析构函数被调用(如果适用于该类型),而delete只是删除为阵列分配的内存。

这里有一个很好看的:http://www.informit.com/guides/content.aspx?g=cplusplus&seqNum=287

没有,数组大小是不存储在C++。(谢谢大家指出这个说法是不准确的。)

+0

不同意你最后的陈述。编译器必须知道数组大小才能调用数组中每个对象的析构函数。我认为你把这一点与C++不对数组进行边界检查的事实混淆起来。 – 2008-10-31 03:39:19

+0

哦,真的。我以为你在暗示大小将作为数组数据结构(缓冲区)的一部分进行存储。是的,编译器可能需要在某处存储大小信息... – 2008-10-31 04:07:28

+0

一种方法是在数组开始之前在单词中存储元素的大小和数量。这被称为cookie。 – Dynite 2008-10-31 09:16:01

6

由于其他人似乎都错过了你的问题的重点,我只是补充说,我在一年前有同样的想法,并且从来没有能够得到答案。

我能想到的唯一的事情是,有额外的开销非常点点对待单个对象作为数组(不必要的“for(int i=0; i<1; ++i)”)

8

妈的,我错过了问题的整点,但我将留下原始答案作为旁注。为什么我们删除[]是因为很久以前我们删除了[cnt],即使在今天,如果您编写delete [9]或delete [cnt],编译器也会忽略[]之间的内容,但编译成功。那时,C++首先被前端处理,然后被送到普通的C编译器。他们无法做到在帐幕下方存放帐号的伎俩,也许他们当时甚至想不起来。而为了向后兼容,编译器很可能使用[]中给出的值作为数组的计数,如果没有这样的值,那么他们从前缀中获得计数,所以它可以两种方式工作。稍后,我们在[]和所有工作之间都没有输入任何内容。今天,我不认为“删除[]”是必要的,但是实现需要这种方式。

我原来的答复(即忽略了一点)::

“删除”删除单个对象。 “delete []”删除一个对象数组。为了使delete []起作用,实现保留了数组中元素的数量。我只是通过调试ASM代码来检查这一点。在我测试的实现(VS2005)中,计数被存储为对象数组的前缀。

如果您在单个对象上使用“delete []”,则count变量是垃圾,因此代码会崩溃。如果您使用“删除”对象数组,由于一些不一致,代码崩溃。我刚才测试了这些案例!

“删除只是删除分配给阵列的内存”。在另一个答案中的陈述是不正确的。如果对象是一个类,delete将调用DTOR。只要在DTOR代码中放置一个断点并删除该对象,断点就会命中。

我想到的是,如果编译器&库假定所有由“new”分配的对象都是对象数组,那么对单个对象或对象数组调用“delete”是可以的。单个对象仅仅是具有1计数对象数组的特殊情况,也许有我丢失的东西,反正...

5

因为没有其他答案目前解决它添加此:

阵列delete[]不能在指针到基类的类上使用 - 当编译器在调用new[]时存储对象的数量时,它不会存储对象的类型或大小(如David所指出的那样,在C++中,您很少支付对于你没有使用的功能)。但是,标量delete可以通过基类安全地删除,所以它既用于正常对象清理和多形清理:

struct Base { virtual ~Base(); }; 
struct Derived : Base { }; 
int main(){ 
    Base* b = new Derived; 
    delete b; // this is good 

    Base* b = new Derived[2]; 
    delete[] b; // bad! undefined behavior 
} 

然而,在相反的情况下 - 非虚析 - 标量delete应尽可能廉价尽可能 - 它不应该检查对象的数量,也不要检查被删除的对象的类型。这使得删除内置型或纯老的数据类型很便宜,因为编译器只需要调用::operator delete没有别的:

int main(){ 
    int * p = new int; 
    delete p; // cheap operation, no dynamic dispatch, no conditional branching 
} 

虽然不是一个详尽的治疗内存分配的,我希望这有助于澄清在C++中可用的内存管理选项的广度。

0

我对Aaron的回答有些困惑,坦率地承认我并不完全理解为什么和在哪里需要删除[]。

我用他的示例代码做了一些实验(修复了几个拼写错误之后)。这是我的结果。 错别字:〜基地需要一个函数体 基地* B被宣布两次

struct Base { virtual ~Base(){ }>; }; 
struct Derived : Base { }; 
int main(){ 
Base* b = new Derived; 
delete b; // this is good 

<strike>Base</strike> b = new Derived[2]; 
delete[] b; // bad! undefined behavior 
} 

编译和执行

[email protected]:g++ -o atest atest.cpp 
[email protected]: ./atest 
[email protected]: # No error message 

与删除[]除去

struct Base { virtual ~Base(){}; }; 
struct Derived : Base { }; 

int main(){ 
    Base* b = new Derived; 
    delete b; // this is good 

    b = new Derived[2]; 
    delete b; // bad! undefined behavior 
} 

编译和执行修改的程序

[email protected]:g++ -o atest atest.cpp 
[email protected]: ./atest 
atest(30746) malloc: *** error for object 0x1099008c8: pointer being freed was n 
ot allocated 
*** set a breakpoint in malloc_error_break to debug 
Abort trap: 6 

当然,我不知道delete [] b实际上是否在第一个例子中工作;我只知道它没有给出编译器错误信息。