堆腐败:原因是什么?

时间:2009-10-01 14:20:41

标签: c++ memory-management

我正在调查由于堆损坏导致的崩溃。由于这个问题非常重要,并涉及分析堆栈和转储结果,因此我决定对与崩溃相关的文件进行代码审查。

坦率地说,我没有深入了解何时堆可能已损坏。

如果你能提出可能导致堆损坏的情况,我将不胜感激。

平台: Windows XP

语言: C ++

编译器: VC6

13 个答案:

答案 0 :(得分:37)

常见方案包括:

  • 在数组的分配空间(char *stuff = new char[10]; stuff[10] = 3;
  • 之外写字
  • 投射到错误的类型
  • 未初始化的指针
  • 错误 - >>和。
  • 使用*和&时的错字错误(或其中之一)

[编辑]从评论中,再补充一点:

  • 将new []和new与delete []混合并删除
  • 缺少或不正确的复制构造函数
  • 指向垃圾的指针
  • 对同一数据多次调用删除
  • 没有虚拟析构函数的多态基类

答案 1 :(得分:13)

欢迎来到地狱。没有简单的解决方案,所以我只提供一些指示。

尝试重现调试环境中的错误。调试器可以使用绑定检查填充堆分配,并告诉您是否在这些绑定检查中编写。此外,它将使用相同的虚拟地址一致地分配内存,使再现性更容易。

在这种情况下,您可以尝试使用Purify等分析工具。他们会检测到你的代码正在做的任何令人讨厌的事情,但也会非常缓慢地运行。这样的工具将检测超出范围的内存访问,释放内存访问,尝试释放两次相同的块,使用错误的分配器/解除分配器等...这些都是可以保持潜伏很长时间而且只是崩溃的所有条件在最不合时宜的时刻。

答案 2 :(得分:6)

您可以查看Advanced Windows Debugging book中的示例章节,其中提供了各种堆损坏示例。

编辑:如果您正在使用可能在更改期间移动元素的stl容器(即vector,deque),请确保您不会在可能更改它的操作中保留对这些元素的引用。

答案 3 :(得分:6)

有些产品会观察内存分配和解除分配,并生成异常报告。最后我用了一个,它们并不便宜,但我不知道现在情况如何。但是,找到VC ++ 6的东西可能是个问题。

请记住,您可能会比崩溃更容易发生堆损坏,因此请注意问题报告,并修复所有堆损坏。

我建议在任何地方使用std::tr1::smart_ptr<>而不是原始指针,但我不确定VC ++ 6是否会支持它。

你为什么还在使用VC ++ 6?升级是否可行?使用更新版本的工具更好,并且它们完全支持智能指针。

答案 4 :(得分:3)

查看this相关问题的答案。

我建议的answer提供了一种技术,可以让你回到实际导致堆损坏的代码。我的回答描述了使用gdb的技术,但我确信你必须能够在Windows上做类似的事情。

原则至少应该是一样的。

答案 5 :(得分:3)

每次执行未在语言标准中定义的内容时,它都是未定义的行为,并且它可能表现出来的一种方式是通过堆损坏。在C ++中有大约三百万种方法可以做到这一点,所以真的不可能。

一些常见的情况是双重释放动态分配的内存,或者在数组的边界外写入。或写入未初始化的指针。

Microsoft的编译器的最新版本添加了/ analyze编译器开关,它执行一系列静态分析来捕获此类错误。在Linux上,valgrind是一个显而易见的选择。

当然,您使用的VC6多年来一直不受支持,并且有许多已知错误,导致生成无效代码。

如果可能,您应该升级到合适的编译器。

答案 6 :(得分:3)

另一个调试技巧是查看使用原始内存视图写入错误位置的值。是写零...字符串...其他一些可识别的数字模式?如果您在错误发生后的某个时刻可以看到损坏的内存,那么可以提供导致它的代码的提示。

即使在析构函数中,删除它们之后也始终将指针设置为null。有时,在基类中的析构函数链中可以触发意外事物,而不是访问部分删除的子类。

答案 7 :(得分:2)

free()或删除分配的内存多于一个常见的错误。在这样的调用之后插入类似* var = NULL的东西并在调用free时检查!= NULL可能会有所帮助。虽然在C ++中使用NULL变量调用 delete 是合法的,但调用C - free ()将会失败。

另一个常见问题是混淆删除和删除[]。

必须使用删除发布使用分配的变量。

分配了 new [] 的数组必须与 delete [] 一起发布。

另外,请确保不要将C风格的内存管理(malloc,calloc,free)与C ++风格的内存管理(new / delete)混合使用。在遗留代码中,通常两者都是混合的,但分配给它的东西不能与另一个分开。

编译器通常无法识别所有这些错误。

答案 8 :(得分:2)

在释放堆内存期间,需要删除第一个子内存块,然后禁用内存块,否则子内存块会被中断泄露,导致数百万次运行该工具后崩溃。 例如:

constQ= new double* [num_equations];
for(int i=0;i<num_equations;i++)
{
constQ[i]=new double[num_equations];
for(int j=0;j<num_equations;j++)
{
constQ[i][j]=0.0;
}
.
.
.

//Deleting/Freeing memory block 
//Here the below only parent memory block is deleted, the child memory block is leaked.

if(constQ!=NULL)
{
delete[] constQ;
constQ=NULL
} 
//Correct way of deleting heap memory..First delet child block memory and then Parent block

if(constQ!=NULL)
{
for(int i=0; i <num_equations;i++)
{
delete[] constQ[i];
delete[] constQ;
constQ=NULL
}

答案 9 :(得分:1)

如果您可以访问* nix机器,则可以使用Valgrind。

答案 10 :(得分:1)

我遇到的最困难的内存损坏错误涉及(1)调用返回std::vector的DLL中的函数,然后(2)让std::vector超出范围(基本上是std::vector)的重点。不幸的是,事实证明DLL已链接到C ++运行时的一个版本,并且该程序已链接到另一个版本;这意味着该库正在调用new[]的一个版本,而我正在调用delete[]的完全不同的版本。

这不是这里发生的事情,因为每次失败并根据你的一条评论“这个错误表现在一百万次崩溃中。”我猜想有一个if语句在一百万次中被捕获一次并导致双delete个错误。

我最近使用了可能对您有所帮助的两种产品的评估版:IBM's Rational PurifyIntel Parallel Inspector。我确定还有其他人(Insure ++被提到很多)。在Linux上你会使用Valgrind。

答案 11 :(得分:0)

您是否认为使用gflags隔离损坏的来源? 一旦你有一个转储(或破坏调试器 - &gt; WinDBG),你可以更准确地看到腐败的原因。

以下是一些gflag示例: http://blogs.msdn.com/b/webdav_101/archive/2010/06/22/detecting-heap-corruption-using-gflags-and-dumps.aspx

干杯,Seb

答案 12 :(得分:0)

这些是HeapAlloc功能语法。

LPVOID WINAPI HeapAlloc(
  _In_ HANDLE hHeap,
  _In_ DWORD  dwFlags,
  _In_ SIZE_T dwBytes
);

dwFlags参数可以有HEAP_GENERATE_EXCEPTIONSHEAP_NO_SERIALIZEHEAP_ZERO_MEMORY

在我们的文件中,我们必须检查我们设置的标志。 如果我们将标志值设置为HEAP_NO_SERIALIZE,则不会有序列化,这意味着多个线程将访问可能导致内存损坏的资源。

&#34;设置HEAP_NO_SERIALIZE值可以消除堆上的互斥。没有序列化, 使用相同堆句柄的两个或多个线程可能会尝试同时分配或释放内存, 可能导致堆中的腐败。&#34;

所以我认为由于堆中的内存损坏,节点崩溃了。

相关问题