【问题标题】:Heap corruption: What could the cause be?堆损坏:原因可能是什么?
【发布时间】:2010-12-03 00:28:08
【问题描述】:

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

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

如果您能提出可能导致堆损坏的方案,我将不胜感激。

平台:Windows XP

语言:C++

编译器:VC6

【问题讨论】:

  • 嗯,可能有点笼统...建议您提供更多(以及一些)详细信息...
  • 您的问题缺少很多细节。你能指定平台和上下文吗?
  • 我已经使用 Compuware 的 BoundsChecker 成功追踪了一些奇怪的内存错误。它不是免费的,但我发现它非常有用,尤其是对于追踪一些非常模糊的错误。
  • 不可能为此提供代码示例。您不妨问“无法编译的代码是什么样的”。有无数种可能性。编程 C++(或任何其他语言)的关键是遵守该语言允许的内容。你不能反其道而行之,试着编译一个列表“只要我不做这些事情,我的代码就是有效的C++”
  • 大多数情况下,如果您的代码部分似乎对损坏堆有怀疑,您可以插入 _heapchk(请参阅msdn.microsoft.com/en-us/library/aa298379%28VS.60%29.aspx)来检查堆是否仍然正常或已经损坏。

标签: c++ memory-management


【解决方案1】:

常见的场景包括:

  • 在数组的分配空间之外写入 (char *stuff = new char[10]; stuff[10] = 3;)
  • 转换为错误的类型
  • 未初始化的指针
  • -> 和 .
  • 的拼写错误
  • 使用 * 和 &(或两者的倍数)时出现拼写错误

[编辑] 来自 cmets,还有一些:

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

【讨论】:

  • 使用指向垃圾的指针。我有一个相当阴险的。常量 WCHAR * cstr = ICUString.c++str().c_str();语法是错误的,但基本上,我从 ICUString 到 std::wstring 到 const WCHAR *,但由于 std::wstring 是一个匿名对象,它在行完成后被删除,因此我的指针指向垃圾。访问它会导致损坏和崩溃。
  • 不要忘记对堆上同一个对象的两个不同指针调用 delete
  • 补充:将随机/已删除的指针传递给delete,将new/new[]delete/delete[] 混合,缺少或不正确的复制ctors/赋值运算符,多态使用具有非虚拟 dtor 的基类...这个列表非常 长。基本上可以归结为我在这里总结的内容:stackoverflow.com/questions/1346583/1346631#1346631
  • 所有伟大的补充。生病将它们添加到列表中:)
  • @Kelly:它将NULL 指针传递给delete,它什么也没做。将相同的指针传递给delete 两次是一个致命错误,导致所谓的未定义行为。通常这会在你身上引发令人讨厌的鼻恶魔。
【解决方案2】:

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

尝试在调试环境中重现错误。调试器可以用绑定检查填充你的堆分配,并会告诉你是否写了这些绑定检查。此外,它将始终使用相同的虚拟地址分配内存,从而更容易重现。

在这种情况下,您可以尝试使用 Purify 等分析工具。他们会检测到你的代码正在做的几乎任何令人讨厌的事情,但也会运行得很慢。这样的工具将检测越界内存访问、释放内存访问、尝试释放两次相同的块、使用错误的分配器/释放器等......这些都是可以潜伏很长时间并且只会崩溃的条件在最不合时宜的时刻。

【讨论】:

  • 很遗憾,这种崩溃并不容易产生,几乎百万次才会发生一次
  • 我明白了。但是,即使错误以百万分之一的崩溃形式出现,分析工具仍然可以很容易地检测到它。
【解决方案3】:

有些产品会观察内存分配和释放,并生成异常报告。上次我用了一个,不便宜,但不知道现在是什么情况。然而,为 VC++ 6 寻找东西可能是个问题。

请记住,您遇到堆损坏的次数比崩溃的次数要多得多,因此请注意问题报告,并修复所有堆损坏。

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

为什么你还在使用 VC++ 6?升级实用吗?这些工具在较新的版本中更好,并且它们完全支持智能指针。

【讨论】:

  • 谢谢大卫,你的 cmets 很有价值,从 VC++ 6 迁移是一个重大决定,它是一个遗留代码,所以有一些可能的改变,是的,将来我想使用智能指针 :) 谢谢
【解决方案4】:

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

编辑:如果您使用的 stl 容器可能会在更改期间移动元素(即向量、双端队列),请确保您不会在可能更改它的操作中保留对此类元素的引用。

【讨论】:

  • 谢谢,我觉得这本书很有用,会一直读下去
【解决方案5】:

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

另外一个常见的问题是混淆delete和delete []。

new分配的变量必须用delete释放。

new []分配的数组必须用delete[]释放。

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

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

【讨论】:

  • 感谢 StefanWoe 一个基本问题,如果是做 new[] 但使用删除而不是删除,它将如何导致堆损坏,可以重新分配剩余的内存吗?
  • 如果某些内容已损坏,重新分配是没有意义的。你迷路了。
  • 我的英语不好:),我的意思是说如果我初始化 char *ptr=new char[100] 并删除 ptr,它会只释放第一个 char..如果是的话发生在 99 的其余时间
  • 在这种情况下会发生什么,规范未明确定义。最可能的结果是对数组中的第一个元素调用析构函数,并释放整个内存块。然而,崩溃、泄漏或堆损坏也可能发生。
  • 未定义调用“new[]”然后“delete”的行为。它可能只释放第一个元素,使程序崩溃,煮咖啡等等。
【解决方案6】:

查看this相关问题的答案。

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

至少原理应该是一样的。

【讨论】:

    【解决方案7】:

    每次您执行语言标准中未定义的操作时,它都是未定义的行为,它可能表现出来的一种方式是通过堆损坏。在 C++ 中有大约 300 万种方法可以做到这一点,所以真的很难说。

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

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

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

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

    【讨论】:

      【解决方案8】:

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

      即使在析构函数中删除指针后,也总是将指针设置为空。有时,在基类中的析构函数链中可能会触发意想不到的事情,而不是导致访问部分删除的子类。

      【讨论】:

        【解决方案9】:

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

        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
        }
        

        【讨论】:

          【解决方案10】:

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

          【讨论】:

            【解决方案11】:

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

            这不是这里发生的事情,因为每次都失败,并且根据您的一位 cmets 的说法,“该错误通过百万分之一的崩溃表现出来。”我猜想有一个 if 语句被一百万次使用一次,它会导致双重 delete 错误。

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

            【讨论】:

            • 即使是您的std::vector 错误,在某些情况下,也可能只会导致百万分之一的崩溃,该向量将其内存传递到错误的堆以进行删除。这是未定义行为的本质,​​它的结果是未定义
            【解决方案12】:

            您是否想过使用 gflags 隔离损坏的根源? 一旦有了转储(或破坏调试器 -> WinDBG),您就可以更准确地看到损坏的原因。

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

            干杯,塞布

            【讨论】:

              【解决方案13】:

              这些是 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,则不会进行序列化,这意味着多个线程将访问可能导致内存损坏的资源。

              “设置HEAP_NO_SERIALIZE值消除了堆上的互斥。没有序列化, 使用相同堆句柄的两个或多个线程可能会尝试同时分配或释放内存, 可能会导致堆损坏。”

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

              【讨论】:

                猜你喜欢
                • 2015-06-10
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2010-11-01
                • 1970-01-01
                • 2013-02-16
                • 2021-09-01
                相关资源
                最近更新 更多