首先,根据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 ++实现上的内存泄漏?
-
对于所有答复者:问题是它如何仅导致内存泄漏,即,如何可能不会导致堆损坏。
-
很容易。 这完全取决于内存管理的编写方式。 由于这是标准未定义的,所有答案都只是推测(但是我敢肯定,我可以编写一个不会使堆崩溃但会泄漏内存的版本)。 内存管理子系统尽可能快速高效。 该标准为他们提供了一套可以优化子系统的前提条件。 打破这些条件,您将获得未定义的行为(可能是堆损坏)。 在调试中,稳定性而不是速度是内存子系统的目标。 因此,泄漏的可能性更大。
-
stackoverflow.com/questions/1553382/
假设我是C ++编译器,并且按以下方式实现内存管理:我在保留内存的每个块前都加了以字节为单位的内存大小。像这样的东西;
1 2 3
| | size | data ... |
^
pointer returned by new and new[] |
请注意,就内存分配而言,new和new[]之间没有区别:两者都只分配一定大小的内存块。
现在,为了调用正确数量的析构函数,delete[]将如何知道数组的大小?只需将存储块的size除以sizeof(T),其中T是数组元素的类型。
现在假设我将delete实现为对析构函数的一个简单调用,然后释放size字节,那么后续元素的析构函数将永远不会被调用。这导致泄漏后续元素分配的资源。但是,因为我释放了size个字节(而不是sizeof(T)个字节),所以不会发生堆损坏。
-
竖起大拇指。就像您刚刚说过的那样,OP假设new和new []的处理方式不同,但是事实并非如此。" new"可能只是" new []",其中size_t的值是1。
-
我实际上是说size来指示字节数,而不是元素。像malloc这样的函数可以完成的工作。我会稍微修改我的帖子以使其明确。
-
如果使用了内存管理技术。但是,您要保留x字节的开销来保存大小,对于小对象,这会增加100%。是的,如果我们想补偿不良的程序员,我们可以支付这笔费用。但是我不想为支持锐齿而付出这个代价,所以我宁愿内存管理非常高效(即使对于小型类型)。结果,该标准不需要,并且大多数实现都不在发行版本的新大小之前。尽管某些版本在调试版本中只是为了帮助调试/分析。
-
@Thomas:是的,但这是实现内存管理的非常人为,强制和从未使用过的方法。当然,它不能解释流行的"内存泄漏"传说是如何产生的。
-
一切都很好,但是如果总是这样的话,那么根本就不需要" delete []"构造,因为运行时库足够聪明以找出要释放多少个对象。该规范不需要分配使用任何特定的实现,因此对于所有实际目的,行为是不确定的,应避免。
-
@MikeCollins当然,但这不是问题。
关于混合new[]和delete据说导致内存泄漏的童话就是这样:一个童话。它在现实中绝对没有立足之地。我不知道它来自哪里,但到现在它已经获得了自己的生命,并且像病毒一样生存,通过口耳相传从一个初学者传播到另一个初学者。
这种"内存泄漏"废话背后最可能的理由是,从无辜的观点来看,delete和delete[]之间的区别是delete仅用于破坏一个对象,而delete[]则用于破坏数组。对象("许多"对象)。通常由此得出的简单结论是,数组的第一个元素将被delete破坏,而其余元素将持续存在,从而导致所谓的"内存泄漏"。当然,至少对典型堆实现有基本了解的任何程序员都将立即了解,最可能的结果是堆损坏,而不是"内存泄漏"。
天真的"内存泄漏"理论的另一个流行解释是,由于调用了错误数目的析构函数,因此数组中对象所拥有的辅助内存不会被释放。这可能是正确的,但是显然这是一个非常强制的解释,面对堆损坏更为严重的问题,这几乎没有任何意义。
简而言之,混合使用不同的分配函数是导致可靠,不可预测和非常实际的不确定行为的那些错误之一。对这种不确定行为的表现施加任何具体限制的任何尝试,都是在浪费时间,并且肯定是缺乏基本了解的迹象。
无需添加,new/delete和new[]/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(在某些情况下)专业的答案应指向参考。例如S.Meyerss有效的C ++项目5:"如果使用[],会发生什么情况?结果是不确定的。即使对于内置类型,结果也是不确定的……那么规则很简单:如果在呼叫新电话时使用[],在呼叫删除电话时必须使用[]。如果在呼叫新电话时不使用[],则在呼叫删除电话时不要使用[]。"
除了导致不确定的行为外,最直接的泄漏原因还在于实现中没有为除数组中的第一个对象之外的所有对象调用析构函数。如果对象分配了资源,显然会导致泄漏。
这是我可能想到的最简单的类:
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:请注意,构造函数也无法在许多其他编程错误中被调用:非虚拟基类析构函数,对智能指针的虚假依赖,...
-
@Suma:我试图在此处显示的问题是如何仅调用第一个对象的析构函数,导致9个泄漏的块包含1个char。您对A元素的数组是正确的,但这不是问题。
-
@Suma:指出解释有些隐瞒,这没有什么害处。感谢您的批评,我们需要!
为什么答案不能同时引起两者呢?
显然,无论是否发生堆损坏,内存都会泄漏。
或者更确切地说,因为我可以重新实现new和delete .....它根本不会导致任何问题。从技术上讲,我可以使new和delete执行new []和delete []。
因此:未定义的行为。
-
是的,它可能同时导致这两种情况,但是IMO一旦出现堆损坏,就不再需要担心内存泄漏。
-
关键是它的不确定性。对于大多数编译器来说,已知答案是什么都没有关系。如果偶然有一个编译器以完全相同的方式实现new和new [],delete和delete [],那么就不会发生堆损坏。因此,问题的答案是,如果编译器以避免堆损坏的方式实现,则可能仅导致内存泄漏。除非编译器以避免两者的方式实现。因此,除非我们引用特定的编译器,否则回答这个问题毫无意义。
我正在回答一个被标记为重复的问题,所以我只在这里复制它以防万一。有人在我之前说过内存分配的工作方式,我只是解释原因和结果。
谷歌附近的一件事:http://en.cppreference.com/w/cpp/memory/new/operator_delete
无论如何,delete是单个对象的功能。它从指针释放实例,然后离开;
delete []是用于取消分配数组的函数。这意味着,它不仅释放了指针,还释放了指针。它将该数组的整个内存块声明为垃圾。
在实践中,这都很酷,但是您告诉我您的应用程序有效。您可能想知道...为什么?
解决方案是C ++无法修复内存泄漏。如果使用不带括号的delete,它将只删除该数组作为对象-这可能会导致内存泄漏。
很酷的故事,内存泄漏,我为什么要关心?
如果分配的内存没有被删除,则会发生内存泄漏。这样,该内存就不需要了不必要的磁盘空间,这将使您毫无理由地丢失有用的内存。那是不好的编程,您可能应该在系统中修复它。