29

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

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

如果类型 new[]'ed 具有微不足道的构造函数和析构函数 VC++ 只是使用new而不是new[]使用delete该块工作正常 -new只需调用“分配内存”,delete只需调用“空闲内存”。

如果类型 new[]'ed 具有非平凡的构造函数或析构函数,则上述技巧无法完成 - VC++7 必须调用正确数量的析构函数。所以它在数组前面加上一个size_t存储元素的数量。现在返回的地址new[]指向第一个元素,而不是块的开头。因此,如果delete使用它只调用第一个元素的析构函数,并且调用“空闲内存”,其地址与“分配内存”返回的地址不同,这会导致 HeapFree() 内部出现一些错误指示,我怀疑这是指堆腐败。

然而,到处都可以读到使用deleteafternew[]会导致内存泄漏的错误语句。我怀疑任何大小的堆损坏都比仅对第一个元素调用析构函数并且可能未调用的析构函数没有释放堆分配的子对象这一事实重要得多。

delete使用after怎么new[]可能只导致某些 C++ 实现的内存泄漏?

4

10 回答 10

30

假设我是一个 C++ 编译器,我像这样实现我的内存管理:我在每个保留内存块前面加上内存大小,以字节为单位。像这样的东西;

| size | data ... |
         ^
         pointer returned by new and new[]

new请注意,在内存分配方面,和没有区别new[]:两者都只是分配一定大小的内存块。

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

现在假设我delete只调用一次析构函数,然后释放size字节​​,那么后续元素的析构函数将永远不会被调用。这导致泄漏由后续元素分配的资源。然而,因为我的是空闲size字节(不是sizeof(T)字节),所以不会发生堆损坏。

于 2009-12-16T09:24:52.130 回答
14

关于混合new[]delete据称导致内存泄漏的童话故事就是这样:一个童话故事。它在现实中绝对没有立足之地。我不知道它是从哪里来的,但现在它获得了自己的生命,像病毒一样存活下来,通过口耳相传从一个初学者传播到另一个初学者。

这种“内存泄漏”胡说八道背后最可能的理由是,从天真幼稚的角度来看,delete和之间的区别delete[]delete用于销毁一个对象,而delete[]销毁一组对象(“许多”对象)。通常由此得出的一个幼稚结论是,数组的第一个元素将被 销毁delete,而其余元素将持续存在,从而产生所谓的“内存泄漏”。当然,任何对典型堆实现至少有基本了解的程序员都会立即明白,最可能的后果是堆损坏,而不是“内存泄漏”。

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

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

不用补充,new/delete其实new[]/delete[]是两个独立的内存管理机制,可以独立定制。一旦它们被定制(通过替换原始内存管理功能),就绝对无法开始预测如果它们混合会发生什么。

于 2009-12-19T02:12:10.980 回答
7

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

也就是说,推理 C++ 中未定义的行为总是毫无意义的练习。如果 new[]/delete 组合碰巧起作用、“仅”泄漏或导致堆损坏,为什么这很重要?你不应该那样编码,期间!而且,在实践中,我会尽可能避免手动内存管理——STL 和 boost 的存在是有原因的。

于 2009-12-16T10:21:25.970 回答
4

如果除了数组中的第一个元素之外的所有元素都没有调用的非平凡析构函数应该释放一些内存,那么您会出现内存泄漏,因为这些对象没有被正确清理。

于 2009-12-16T09:19:49.060 回答
3

除了导致未定义的行为之外,泄漏的最直接原因在于实现没有为数组中的第一个对象以外的所有对象调用析构函数。如果对象已分配资源,这显然会导致泄漏。

这是我能想到的导致这种行为的最简单的类:

 struct A { 
       char* ch;
       A(): ch( new char ){}
       ~A(){ delete ch; }
    };

A* as = new A[10]; // ten times the A::ch pointer is allocated

delete as; // only one of the A::ch pointers is freed.

PS:请注意,在许多其他编程错误中也无法调用构造函数:非虚拟基类析构函数,对智能指针的错误依赖,...

于 2009-12-16T09:20:51.093 回答
3

在析构函数释放内存的任何情况下,它都会导致所有 C++ 实现中的泄漏,因为析构函数永远不会被调用。

在某些情况下,它可能会导致更严重的错误。

于 2009-12-16T09:21:34.070 回答
3

如果 new() 运算符被覆盖但 new[] 未被覆盖,则可能会发生内存泄漏。delete / delete[] 运算符也是如此

于 2009-12-16T09:25:48.643 回答
3

迟到的答案,但...

如果您的删除机制只是调用析构函数并将释放的指针连同 sizeof 暗示的大小一起放到空闲堆栈上,那么在使用 new[] 分配的一块内存上调用 delete 将导致内存丢失 - 但是不是腐败。更复杂的 malloc 结构可能会破坏或检测到这种行为。

于 2010-03-16T10:09:49.870 回答
2

为什么答案不能是它导致两者?

显然,无论是否发生堆损坏,内存都会泄漏。

或者更确切地说,因为我可以重新实现 new 和 delete .....难道它根本不会造成任何事情。从技术上讲,我可以使 new 和 delete 执行 new[] 和 delete[]。

因此:未定义的行为。

于 2011-02-28T16:10:30.960 回答
1

我正在回答一个被标记为重复的问题,所以我会在这里复制它以防万一。内存分配的工作方式在我之前就已经说过了,我将只解释原因和影响。

只是谷歌的一件小事:http: //en.cppreference.com/w/cpp/memory/new/operator_delete

无论如何,delete是针对单个对象的函数。它从指针中释放实例,然后离开;

delete[]是一个用于释放数组的函数。这意味着,它不只是释放指针;它将该数组的整个内存块声明为垃圾。

这在实践中很酷,但你告诉我你的应用程序有效。您可能想知道...为什么?

解决方案是 C++ 不修复内存泄漏。如果您使用不带括号的删除,它将仅删除作为对象的数组 - 一个可能导致内存泄漏的过程。

很酷的故事,内存泄漏,我为什么要关心?

当分配的内存没有被删除时,就会发生内存泄漏。然后,该内存需要不必要的磁盘空间,这将使您几乎无缘无故地丢失有用的内存。那是糟糕的编程,你可能应该在你的系统中修复它。

于 2015-02-04T21:17:42.070 回答