关于c ++:如何将new []与delete配对可能仅导致内存泄漏?

How could pairing new[] with delete possibly lead to memory leak only?

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

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

如果new [] ed类型具有简单的构造函数和析构函数,则VC ++只会使用new而不是new[]并将该块使用delete可以正常工作-new仅调用"分配内存",delete仅调用"空闲内存"。

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

然而,在这里到那里的每个人都可以读取错误的语句,这些语句在new[]之后使用delete会导致内存泄漏。我怀疑堆损坏的任何大小都比仅针对第一个元素调用析构函数且未调用的析构函数没有释放堆分配的子对象这一事实更为重要。

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


假设我是C ++编译器,并且按以下方式实现内存管理:我在保留内存的每个块前都加了以字节为单位的内存大小。像这样的东西;

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

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

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

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


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

这种"内存泄漏"废话背后最可能的理由是,从无辜的观点来看,deletedelete[]之间的区别是delete仅用于破坏一个对象,而delete[]则用于破坏数组。对象("许多"对象)。通常由此得出的简单结论是,数组的第一个元素将被delete破坏,而其余元素将持续存在,从而导致所谓的"内存泄漏"。当然,至少对典型堆实现有基本了解的任何程序员都将立即了解,最可能的结果是堆损坏,而不是"内存泄漏"。

天真的"内存泄漏"理论的另一个流行解释是,由于调用了错误数目的析构函数,因此数组中对象所拥有的辅助内存不会被释放。这可能是正确的,但是显然这是一个非常强制的解释,面对堆损坏更为严重的问题,这几乎没有任何意义。

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

无需添加,new/deletenew[]/delete[]实际上是两个独立的内存管理机制,可以独立自定义。一旦对它们进行了自定义(通过替换原始内存管理功能),绝对没有办法甚至开始预测如果将它们混在一起会发生什么。


看来您的问题确实是"为什么不发生堆损坏?"。答案是"因为堆管理器跟踪分配的块大小"。让我们回到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()版本的块的大小保存在块的"前面"。回到C ++,通常使用malloc来实现new / delete和new [] / delete [](尽管它们不一定必须实现,但您不应该依赖它)。这就是为什么new [] / delete组合不会破坏堆的原因-删除将释放适当数量的内存,但是,正如我之前的每个人所解释的那样,您可以通过不调用正确数量的析构函数来泄漏内存。

也就是说,对C ++中未定义行为的推理总是毫无意义的。如果new [] / delete组合碰巧起作用,"仅"泄漏或导致堆损坏,那怎么办呢?句号,你不应该那样写!而且,在实践中,我会尽可能避免手动进行内存管理-STL和Boost的存在是有原因的。


如果应该对非平凡的析构函数(而不是数组中的第一个元素)进行全部调用,则应该释放一些内存,则可能会发生内存泄漏,因为这些对象未正确清理。


迟到了答案,但是...

如果您的删除机制只是调用析构函数,然后将释放的指针以及sizeof隐含的大小放到一个可用堆栈上,那么在分配有new []的内存块上调用delete将会导致内存丢失-但不腐败。
更复杂的malloc结构可能破坏或检测此行为。


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


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

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


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

这是我可能想到的最简单的类:

1
2
3
4
5
6
7
8
9
 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:请注意,构造函数也无法在许多其他编程错误中被调用:非虚拟基类析构函数,对智能指针的虚假依赖,...


为什么答案不能同时引起两者呢?

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

或者更确切地说,因为我可以重新实现new和delete .....它根本不会导致任何问题。从技术上讲,我可以使new和delete执行new []和delete []。

因此:未定义的行为。


我正在回答一个被标记为重复的问题,所以我只在这里复制它以防万一。有人在我之前说过内存分配的工作方式,我只是解释原因和结果。

谷歌附近的一件事:http://en.cppreference.com/w/cpp/memory/new/operator_delete

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

delete []是用于取消分配数组的函数。这意味着,它不仅释放了指针,还释放了指针。它将该数组的整个内存块声明为垃圾。

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

解决方案是C ++无法修复内存泄漏。如果使用不带括号的delete,它将只删除该数组作为对象-这可能会导致内存泄漏。

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

如果分配的内存没有被删除,则会发生内存泄漏。这样,该内存就不需要了不必要的磁盘空间,这将使您毫无理由地丢失有用的内存。那是不好的编程,您可能应该在系统中修复它。