如何将new []与删除配对可能只会导致内存泄漏?

sha*_*oth 26 c++ memory-management undefined-behavior

首先,根据C++标准,使用delete分配的任何内容new[]都是未定义的行为.

在Visual C++ 7中,这种配对可能导致两种后果之一.

如果类型new []'ed有简单的构造函数和析构函数VC++只是使用new而不是new[]和使用delete该块工作正常 - new只调用"分配内存",delete只调用"空闲内存".

如果new []'ed类型有一个非平凡的构造函数或析构函数,则无法完成上述技巧 - VC++ 7必须调用恰当数量的析构函数.因此它预先在数组中size_t存储元素的数量.现在,地址返回的地址new[]到第一个元素,而不是块的开头.因此,如果delete使用它只调用第一个元素的析构函数和调用"空闲内存",其地址不同于"分配内存"返回的地址,这导致HeapFree()内部的一些错误指示我怀疑是指堆腐败.

然而,每个人都可以阅读使用deleteafter new[]导致内存泄漏的错误陈述.我怀疑任何大小的堆损坏都比仅为第一个元素调用析构函数并且可能没有调用的析构函数没有释放堆分配的子对象这一事实重要得多.

如何使用delete之后new[]可能只会导致某些C++实现中的内存泄漏?

Tho*_*mas 29

假设我是一个C++编译器,并且我实现了这样的内存管理:我将每个预留内存块添加到内存大小(以字节为单位).像这样的东西;

| size | data ... |
         ^
         pointer returned by new and new[]
Run Code Online (Sandbox Code Playgroud)

注意,就内存分配而言,new和之间没有区别new[]:两者都只分配一定大小的内存块.

现在将如何delete[]知道数组的大小,以便调用正确数量的析构函数?简单地将size内存块除以sizeof(T),T数组元素的类型.

现在假设我delete只是对析构函数执行一次调用,然后释放size字节,然后永远不会调用后续元素的析构函数.这导致后续元素分配的资源泄漏.然而,因为我免费size字节(而不是sizeof(T)字节),没有发生堆损坏.

  • 如果使用了内存管理技术.但是,你有一个x字节的开销来保持大小,小对象增加了100%.是的,如果我们想补偿糟糕的程序员,我们可以支付这笔费用.但是我不想只为了支持'sharptooth'而支付这个价格,所以我更希望内存管理非常有效(即使是小型的).因此,该标准不需要,并且大多数实现不会在发布版本中添加新的大小.虽然有些人在调试版本中只是为了帮助调试/分析. (3认同)

AnT*_*AnT 12

关于混合new[]delete据称导致记忆泄漏的童话故事就是:一个童话故事.它在现实中完全没有立足点.我不知道它来自哪里,但到现在它已经获得了自己的生命,像病毒一样幸存下来,通过口口相传从一个初学者传播到另一个.

这背后"内存泄漏"无义的最可能的理由是,从视天真幼稚点之间的差值delete,并delete[]delete用来销毁只是一个对象,而delete[]破坏对象的数组("多"的对象).通常由此得出的一个天真的结论是,数组的第一个元素将被破坏delete,而其余元素将持续存在,从而产生所谓的"内存泄漏".当然,任何对典型堆实现至少基本了解的程序员都会立即明白最可能的结果是堆损坏,而不是"内存泄漏".

对于天真的"内存泄漏"理论的另一个流行解释是,由于调用了错误数量的析构函数,因此数组中对象所拥有的辅助内存不会被释放.这可能是真的,但这显然是一种非常强制性的解释,在面对更严重的堆损坏问题时几乎没有相关性.

简而言之,混合不同的分配函数是导致可靠,不可预测和非常实际的未定义行为的错误之一.任何试图对这种不确定行为的表现施加一些具体限制的尝试都只是浪费时间和确定缺乏基本理解的迹象.

不必说,new/deletenew[]/delete[]事实上是两个独立的内存管理机制,这是独立定制的.一旦他们得到定制(通过替换原始内存管理功能),绝对没有办法开始预测如果他们混合可能会发生什么.


sbk*_*sbk 7

看来你的问题确实是"为什么堆腐败不会发生?".答案就是"因为堆管理器会跟踪分配的块大小".让我们回到C一分钟:如果你想在C中分配一个int,你可以这样做int* p = malloc(sizeof(int)),如果你想分配大小的数组,n你可以写int* p = malloc(n*sizeof(int))int* p = calloc(n, sizeof(int)).但无论如何free(p),无论你如何分配它,你都可以释放它.你永远不会将大小传递给free(),free()只是"知道"要释放多少,因为malloc() - ed块的大小保存在块的"前面"某处.回到C++,new/delete和new []/delete []通常用malloc来实现(尽管它们不一定是这样,你不应该依赖它).这就是为什么new []/delete组合不会破坏堆的原因 - 删除将释放适量的内存,但是,正如我之前的每个人所解释的那样,你可以通过不调用正确数量的析构函数来获取泄漏.

也就是说,在C++中推理未定义的行为总是毫无意义的.为什么新的[] /删除组合正常工作,"只"泄漏或导致堆损坏?你不应该这样编码,期间!并且,在实践中,我会尽可能避免手动内存管理 - STL和boost是有原因的.