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

时间:2009-12-16 09:15:12

标签: c++ memory-management undefined-behavior

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

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

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

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

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

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

10 个答案:

答案 0 :(得分:29)

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

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

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

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

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

答案 1 :(得分:12)

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

这种“记忆泄漏”废话背后最可能的理由是,从无辜的天真观点来看,deletedelete[]之间的差异是delete用来摧毁一个object,而delete[]销毁一个对象数组(“很多”对象)。通常由此得出的一个天真的结论是,数组的第一个元素将被delete销毁,而其余元素将持续存在,从而产生所谓的“内存泄漏”。当然,任何对典型堆实现至少基本了解的程序员都会立即明白最可能的结果是堆损坏,而不是“内存泄漏”。

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

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

无需添加,new/deletenew[]/delete[]实际上是两个独立的内存管理机制,可以独立定制。一旦他们得到定制(通过替换原始内存管理功能),甚至无法开始预测如果他们混合可能会发生什么。

答案 2 :(得分: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&提升是有原因的。

答案 3 :(得分:4)

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

答案 4 :(得分: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:请注意,构造函数也无法在许多其他编程错误中被调用:非虚基类析构函数,错误依赖智能指针,...

答案 5 :(得分:3)

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

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

答案 6 :(得分:3)

如果重写new()运算符但新的[]不是,则可能发生内存泄漏。删除/删除[]运算符

也是如此

答案 7 :(得分:3)

迟到的答案,但是......

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

答案 8 :(得分:2)

为什么答案不能导致两者兼而有之?

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

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

HENCE:未定义的行为。

答案 9 :(得分:1)

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

google上的一件小事:http://en.cppreference.com/w/cpp/memory/new/operator_delete

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

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

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

解决方案是C ++无法修复 内存泄漏 。如果你在没有括号的情况下使用delete,它只会删除数组作为对象 - 一个可能导致内存泄漏的进程

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

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