delete[]和delete是否相等?

48
IP_ADAPTER_INFO *ptr=new IP_ADAPTER_INFO[100];

如果我使用free()函数释放内存

delete ptr;

如果不会导致内存泄漏,那么为什么?

这是由VS2005生成的反汇编代码。

; delete ptr;
0041351D  mov         eax,dword ptr [ptr] 
00413520  mov         dword ptr [ebp-0ECh],eax 
00413526  mov         ecx,dword ptr [ebp-0ECh] 
0041352C  push        ecx  
0041352D  call        operator delete (4111DBh) 
00413532  add         esp,4 

; delete []ptr;
00413535  mov         eax,dword ptr [ptr] 
00413538  mov         dword ptr [ebp-0E0h],eax 
0041353E  mov         ecx,dword ptr [ebp-0E0h] 
00413544  push        ecx  
00413545  call        operator delete[] (4111E5h) 
0041354A  add         esp,4 

我已经阅读过,析构函数将被调用数组中的第一个元素,但整个内存将被释放,在调试时可以看到相同的情况。 - Satbir
不,只有第一个元素被释放了,其他的没有。 - Andrejs Cainikovs
1
@Andrej:不,那并不确定。可能会发生那种情况,但也可能不会。对于POD来说,甚至有可能不会发生。但你永远不知道。 - sbi
7
IP_ADAPTER_INFO不再是POD类型时会发生什么?您是否打算编辑所有的代码?由于您已经使用了C++标签,因此应该考虑使用std::vector。(如果需要更详细的上下文信息,请提供更多的背景信息) - Kirill V. Lyadvinsky
我强烈建议忽略这个问题,转而阅读delete vs delete[]。那里的答案更加直接明了。 - Theodore Murdock
Delete 做了两件事情:调用析构函数和释放已分配的内存。在 Visual C++ 中,无论是哪种形式的 delete 都会正确地释放已分配的内存 - 当在数组 new 上使用标量形式的 delete 时,我不会期望它导致内存泄漏或堆破坏。然而,错误的 delete 将因为错误的析构函数调用(只在第一个元素上调用,而应该在数组的每个元素上调用)而引发错误。这可能会导致内存泄漏或堆破坏,具体取决于析构函数的操作(或由于未被调用而未执行的操作)。 - Zenilogix
6个回答

155

无论这是否会导致内存泄漏、擦除硬盘、怀孕、Nasal Demon在公寓里追逐你,还是使一切正常且没有明显的问题,都是未定义的。这可能是在一个编译器中这样,而在另一个编译器中则发生改变,也可能随着新编译器版本、每次编译、月相、你的心情,或者取决于上个阳光明媚的下午通过处理器的中微子数量而有所不同。或者也可能没有。

所有这些以及无限多的其他可能性都归为一个术语:未定义行为

所以请远离它。


1
我认为“未定义行为”是“不好的”,应该避免,就像你所说的一样。但这也意味着,在某些情况下它实际上会泄漏内存,因此您应该始终编写代码以解决最坏情况。这是我的观点。 - Filip Ekberg
7
假设你自己的程序触发了未定义行为,那是一种进化版的防御性编程吗? - Michael Foukarakis
23
+1 表示正确。谈论 deletedelete[] 在某个具体实现中的行为会给人一种错误的想法。这是未定义行为:不要这么做。 - jalf

14

这只是对某些操作系统和编译器中的一些“未定义”行为的说明。希望这能帮助人们调试他们的代码。

测试1

#include <iostream>
using namespace std;
int main()
{
  int *p = new int[5];
  cout << "pass" << endl;
  delete p;
  return 0;
}

测试2

#include <iostream>
using namespace std;
int main()
{
  int *p = new int;
  cout << "pass" << endl;
  delete[] p;
  return 0;
}

测试 3

#include <iostream>
using namespace std;
struct C {
  C() { cout << "construct" << endl; }
  ~C() { cout << "destroy" << endl; }
};

int main()
{
  C *p = new C[5];
  cout << "pass" << endl;
  delete p;
  return 0;
}

测试4

#include <iostream>
using namespace std;
struct C {
  C() { cout << "construct" << endl; }
  ~C() { cout << "destroy" << endl; }
};

int main()
{
  C *p = new C;
  cout << "pass" << endl;
  delete[] p;
  return 0;
}
  • Windows 7 x86,使用msvc 2010编译。默认选项编译,即启用异常处理程序。

测试1

pass

测试 2

pass

测试 3

construct
construct
construct
construct
construct
pass
destroy
# Then, pop up crash msg

测试 4

construct
pass
destroy
destroy
destroy
destroy
destroy
destroy
destroy
... # It never stop until CTRL+C
  • Mac OS X 10.8.5,llvm-gcc 4.2或gcc-4.8会生成相同的输出结果。

测试1

pass

测试2

pass

测试3

construct
construct
construct
construct
construct
pass
destroy
a.out(71111) malloc: *** error for object 0x7f99c94000e8: pointer being freed was not allocated
*** set a breakpoint in malloc_error_break to debug
zsh: abort      ./a.out

测试4

construct
pass
a.out(71035) malloc: *** error for object 0x7f83c14000d8: pointer being freed was not allocated
*** set a breakpoint in malloc_error_break to debug
zsh: abort      ./a.out
  • Ubuntu 12.04,AMD64,gcc 4.7

测试1

pass

测试 2

pass

测试3

construct
construct
construct
construct
construct
*** glibc detected *** ./a.out: munmap_chunk(): invalid pointer: 0x0000000001f10018 ***
======= Backtrace: =========
/lib/x86_64-linux-gnu/libc.so.6(+0x7eb96)[0x7fe81d878b96]
./a.out[0x400a5b]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xed)[0x7fe81d81b76d]
./a.out[0x4008d9]
======= Memory map: ========
....
zsh: abort (core dumped)  ./a.out

测试4

construct
destroy
destroy
destroy
destroy
destroy
destroy
destroy
destroy
...
destroy
destroy
*** glibc detected *** ./a.out: free(): invalid pointer: 0x00000000016f6008 ***
======= Backtrace: =========
/lib/x86_64-linux-gnu/libc.so.6(+0x7eb96)[0x7fa9001fab96]
./a.out[0x400a18]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xed)[0x7fa90019d76d]
./a.out[0x4008d9]
======= Memory map: ========
...
zsh: abort (core dumped)  ./a.out

1
你的测试仍然没有证明是否会出现内存泄漏。 - SOFe

7
通常情况下不会发生内存泄漏,因为在 POD 析构函数是平凡的且没有需要调用它们的必要时,delete 只会释放数组所占用的内存。内存释放只需要一个指针值,因此它将返回到堆中。数组占用连续的内存块,因此释放可以像释放单个元素一样成功。
但不要依赖这种行为,因为它是未定义的行为。也许它能正常工作,也许会发生可怕的事情,在这个编译器上可以工作,在另一个编译器上不能,很多人会感谢你引入了错误。
详情请参见此答案

2
因为new T[]可能会添加一个sizeof偏移量,而不管T是否为POD,此时delete[]将会进行补偿。如果使用delete,则会错过几个字节的头分配块,将(可能未初始化的)元素计数解释为头,并导致不可预测的堆破坏。当然,这只有在老板查看时才会出现。 - MSalters
2
当然,这就是为什么通常会有“usually”这个词。而堆破坏不是一种泄漏。 - sharptooth
3
即使 struct A 是一个POD类型,new A[10] 会分配并存储大小,并且 delete[] 必须检索并将其传递给 operator deletestruct A { void operator delete[](void *p, size_t t) { } }; - Johannes Schaub - litb

6

delete: 调用所指向元素的适当析构函数(如果需要),然后释放内存块。

delete[]: 对其数组中每个元素调用适当的析构函数(如果需要),然后释放内存块。


4
只有当正确使用deletedelete[]时,所有这些才是真实的。这个问题特别涉及到在应该使用delete[]的情况下使用了delete - CB Bailey

5

在使用new T[n]分配内存时,使用delete运算符是未定义的行为,并且不同编译器的结果可能会不同。例如,据我所知,MSVC编译器和GCC生成的代码是不同的。

如果A指向通过new T[n]分配的数组,则必须通过delete[] A来删除它。delete和delete[]之间的区别很简单-前者销毁一个标量对象,后者销毁一个数组。


1
这个编译器 - 我没有看到对特定编译器的引用(不再)?“未定义行为”是ISO标准术语,不指代任何具体实现。 - MSalters
4
C++标准明确规定(至少在我手头拿到的草案版本13.9.2001中如此),这种行为是未定义的:在第一种情况(删除对象)中,delete运算符的操作数必须是指向非数组对象或表示该对象的基类子对象的指针(第1.8节)。如果不是,则行为是未定义的。在第二种情况(删除数组)中,delete运算符的操作数必须是先前的数组new表达式生成的指针值。如果不是,则行为是未定义的。 - Komat
3
在我的 C++98 版本中,这两个都不存在。总之,标准只规定了你在更新前所说的内容:一个销毁纯量对象,另一个销毁数组。但它并没有像你在第一个更新中所说的那样,如果对数组使用标量删除,则销毁第一个对象。这不是标准保证的。这只是未定义的行为。 - jalf
1
感谢jalf,sbi和Komat。你们是正确的,我已经承认了我的错误并更新了帖子。 - Andrejs Cainikovs
1
请更新您的帖子,以便消除所有这些冲突的更新/或者将正确的信息放在顶部。 - Antti Haapala -- Слава Україні
显示剩余7条评论

-3

对于POD数组,它不会(大多数编译器)。例如,MSVCPOD数组的deletedelete[]生成相同的代码。

个人认为C/C++不需要delete[]运算符。编译器知道对象大小和分配的内存大小在运行时已知,因此很容易知道指针是否为数组并以正确的方式处理内存。

编辑:

好的,各位。你们能否测试一下你们的编译器并说一下是否会泄漏?

试着像编译器开发者一样思考。我们有newnew[]deletedelete[]。每个new都有自己的delete。看起来很完美和完整。那么当你调用delete[]时会发生什么呢?

1. call vector destructor for an object
2. actual free memory

析构函数是用来做什么的POD吗?什么也不需要!因此,调用delete释放POD数组不会泄漏内存!即使这违反了标准,甚至不推荐使用。

编辑2:

这是由VS2008生成的反汇编代码:

operator delete[]:
78583BC3  mov         edi,edi 
78583BC5  push        ebp  
78583BC6  mov         ebp,esp 
78583BC8  pop         ebp  
78583BC9  jmp         operator delete (78583BA3h) 

6
delete[] 绝对不是某种形式的优化,它关乎正确性。 - sbi
是的,但这是多余的。人们想知道它是否会泄漏,我回答了。为什么要踩呢? - Sergey Podobry
2
@Sergius:我不能代表其他人,但我对它进行了负面评价,因为你说“对于POD数组,它不会泄漏”-即使后来你有点淡化了这一点。这样做是_未定义的_。句号。(有合理的假设可以推断出可能的情况,但它们绝对不适用于所有编译器甚至编译器版本,因此发表这样的声明似乎是错误的。) - sbi
1
好的,我说过“(使用大多数编译器)”。但不管怎样,还是谢谢!我会在陈述时更加小心。 - Sergey Podobry

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接