【问题标题】:Is delete[] equal to delete?delete[] 是否等于删除?
【发布时间】:2016-06-20 12:13:05
【问题描述】:
IP_ADAPTER_INFO *ptr=new IP_ADAPTER_INFO[100];

如果我免费使用

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 

【问题讨论】:

  • 我已经读过,析构函数将被调用为数组中的第一个元素,但整个内存将被释放,我在调试时也可以看到
  • 不,只有第一个元素被释放,其他元素没有。
  • @Andrej:不,这不确定。它可能会以这种方式发生,但它可能不会。对于 POD,它甚至可能不会。但你永远不知道。
  • IP_ADAPTER_INFO 不再是 POD 类型时会发生什么?您要编辑所有代码吗?您已使用 C++ 标签标记您的问题,因此您应该考虑使用std::vector
  • 我强烈建议您忽略这个问题,转而阅读 [delete vs delete[]](stackoverflow.com/questions/4255598/delete-vs-delete),其答案更重要。

标签: c++ memory-management delete-operator


【解决方案1】:

在带有 new T[n] 的分配上使用 delete 运算符是 undefined 并且会因编译器而异。 AFAIK,例如 MSVC 编译器将生成与 GCC 不同的代码。

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

【讨论】:

  • 没有人(甚至 Bjarne,更不用说一些 Bjorn)指定只有一个对象会被释放。它是未定义的。 (而且,正如我在其他地方所说,我至少使用过一种编译器,它可以释放它们。)
  • 此编译器 - 我没有看到对特定编译器的引用(不再)? “未定义行为”是一个 ISO 标准术语,并不指任何特定的实现。
  • C++ 标准明确声明(至少在我手头的 13.9.2001 的草稿版本中)这种行为是未定义的:在第一种选择(删除对象)中,操作数的值delete 应是指向非数组对象的指针或指向表示此类对象的基类(第 10 条)的子对象(1.8)的指针。如果不是,则行为未定义。在第二种选择(删除数组)中,删除操作数的值应是从前一个数组 new-expression 产生的指针值。72)如果不是,则行为未定义。
  • 我的副本(C++98)中都不存在这些。无论如何,该标准仅指定您在更新之前所说的内容:一个销毁标量对象,另一个销毁数组。它确实 not 说明您在第一次更新中拥有的内容,即如果在数组上调用标量删除,则第一个对象将被销毁。这是标准保证的。它只是未定义。
  • 感谢 jalf、sbi、Komat。你在哪里,我承认我的错误并更新了帖子。
【解决方案2】:

这是否会导致内存泄漏、擦除您的硬盘、让您怀孕、让讨厌的鼻恶魔在您的公寓周围追逐您,或者让一切正常工作而没有明显的问题,都未定义。一个编译器可能会这样,另一个编译器会改变下午。或者它可能不会。

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

远离它。

【讨论】:

  • 我会说“未定义的行为”是“不好的”,应该避免,正如你所说。但这也意味着在某些情况下它实际上会泄漏内存,您应该始终编码以便解决最坏的情况。反正这是我的看法。
  • @Filip:假设您自己的程序调用未定义的行为?这是某种进化形式的防御性编程吗?
  • +1 表示正确。谈论deletedelete[] 在某些特定实现中的行为只会给出错误的想法。这是未定义的行为:不要这样做。
【解决方案3】:

它通常不会泄漏,因为在 POD 的析构函数是微不足道的情况下,不需要调用它们,所以delete 只是释放数组占用的内存。内存释放只需要一个指针值,因此它将返回到堆中。该数组占用一个连续的内存块,因此释放可以成功,就像它是单个元素的释放一样。

但不要依赖它,因为它是未定义的行为。也许它可以正常工作,也许会发生一些可怕的事情,在这个编译器上工作,在另一个编译器上不起作用,许多人感谢你植入错误。

详情请见this answer

【讨论】:

  • 因为new T[] 可能会添加一个sizeof 偏移量,而不管T 的POD-ness,在这种情况下delete[] 将对此进行补偿。 delete 将错过标头分配块几个字节,将(可能未初始化的)元素计数解释为标头,并导致不可预测的堆损坏。当然只有在老板看的时候才会出现。
  • 当然,这就是为什么会有通常这个词。而且堆损坏不是泄漏。
  • struct A { void operator delete[](void *p, size_t t) { } }; 即使struct APODnew A[10] 将分配和存储该大小,delete[] 必须检索它并将其传递给操作员删除
【解决方案4】:

对于 POD 数组,它将不会泄漏(使用最多的编译器)。例如,MSVCdelete 生成 相同 代码,为 POD 数组生成 delete[]强>。

就个人而言,我认为 C/C++ 可能没有运算符 delete[]。编译器知道对象的大小,分配的内存大小在运行时是已知的,因此很容易知道是否是指针数组并以正确的方式分配内存。

编辑:

好的,伙计们。你能在你的编译器上测试并说它是否泄漏吗?

尝试以编译器开发人员的身份思考。我们有newnew[]deletedelete[]。每个都有自己的删除。看起来完美而完整。让我们看看当你调用 delete[] 时发生了什么?

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

POD 的析构函数是什么?没有! 因此,为 POD 数组调用 delete 不会泄漏!即使它打破了标准。即使不推荐。

编辑2:

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

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

【讨论】:

  • delete[] 绝对不是某种形式的优化。一切都是为了正确。
  • 是的,但是是多余的。人们想知道它是否会泄漏,我回答了。为什么要投反对票?
  • @Sergius:我不能代表其他人发言,但我否决了它,因为您说“对于 POD 阵列,它不会泄漏”——尽管您稍后对此有所淡化。它的作用是undefined。时期。 (有一些合理的假设可以推断出可能的情况,但它们肯定不适合所有编译器甚至编译器版本,所以做出这样的陈述对我来说似乎是错误的。)
  • 好吧,我说的是“(使用最多的编译器)”。不过还是谢谢!我的发言会更加谨慎。
【解决方案5】:

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

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

【讨论】:

  • 如果正确使用deletedelete[],所有这些都是正确的。这个问题专门针对应该使用delete[] 而使用delete 的情况。
【解决方案6】:

只是说明某些操作系统和编译器上的一些“未定义”行为。希望对大家调试代码有所帮助。

测试 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

【讨论】:

  • 您的测试仍然无法证明是否存在内存泄漏。
猜你喜欢
  • 1970-01-01
  • 2022-01-20
  • 2011-04-07
  • 1970-01-01
  • 2021-02-17
  • 1970-01-01
  • 2018-12-22
相关资源
最近更新 更多