为什么我们甚至需要“delete []”运算符?

时间:2008-10-31 03:23:10

标签: c++ memory-management heap-memory

这个问题一直困扰着我。我一直认为C ++应该设计成“删除”操作符(没有括号),即使使用“new []”运算符也是如此。

在我看来,写下这个:

int* p = new int;

应该等同于分配1个元素的数组:

int* p = new int[1];

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

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

7 个答案:

答案 0 :(得分:37)

这样就可以调用各个元素的析构函数。是的,对于POD数组,没有太大区别,但在C ++中,您可以拥有具有非平凡析构函数的对象数组。

现在,您的问题是,为什么不让newdelete表现得像new[]delete[],并摆脱new[]和{{1} }?我会回到Stroustrup的“设计和演变”一书,他说如果你不使用C ++功能,你就不应该为它们付费(至少在运行时)。现在的方式是,delete[]new的行为与deletemalloc一样高效。如果free具有delete含义,则在运行时会有一些额外的开销(正如James Curran指出的那样)。

答案 1 :(得分:9)

该死的,我错过了整个问题,但我会将原来的答案留作旁注。为什么我们删除[]是因为很久以前我们删除了[cnt],即使今天你写了删除[9]或删除[cnt],编译器只是忽略了[]之间的东西但编译好了。那时,C ++首先由前端处理,然后送到普通的C编译器。他们无法在窗帘下面的地方存储计数,也许他们当时甚至都想不到它。为了向后兼容,编译器最有可能使用[]之间给出的值作为数组的计数,如果没有这样的值,那么它们从前缀获得计数,因此它可以双向工作。后来,我们在[]和一切工作之间没有输入任何内容。今天,我不认为“删除[]”是必要的,但实现要求它。

我的原始答案(错过了重点)::

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

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

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

我发生的事情是,如果编译器&库假设“new”分配的所有对象都是对象数组,可以为单个对象或对象数组调用“delete”。单个对象就是一个计数为1的对象数组的特殊情况。也许有些东西我不知道了,无论如何......

答案 2 :(得分:6)

由于其他人似乎都错过了你的问题所在,我只想补充说,我在一年前就有同样的想法,而且从未能得到答案。

我唯一能想到的是,将一个对象作为一个数组处理是一个非常小的额外开销(一个不必要的“for(int i=0; i<1; ++i)”)

答案 3 :(得分: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 ++中可用的内存管理选项的广度。

答案 4 :(得分:3)

Marshall Cline有一些info on this topic

答案 5 :(得分:2)

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

这是一个很好的阅读:http://www.informit.com/guides/content.aspx?g=cplusplus&seqNum=287

不,数组大小不会存储在C ++中的任何地方。(感谢大家指出这个语句不准确。)

答案 6 :(得分:0)

我对Aaron的回答感到有些困惑,坦率地承认我并不完全理解为什么以及需要删除[]的地方。

我用他的示例代码做了一些实验(在修正了几个拼写错误之后)。这是我的结果。     错别字:~Base需要一个功能体     Base * 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
}

编译和执行

david@Godel:g++ -o atest atest.cpp 
david@Godel: ./atest 
david@Godel: # 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
}

编译和执行

david@Godel:g++ -o atest atest.cpp 
david@Godel: ./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

当然,我不知道删除[] b是否真的在第一个例子中工作;我只知道它没有给出编译器错误消息。