如何在C ++中调试双删除?

时间:2012-04-09 17:40:41

标签: c++ debugging memory-management

我正在维护用C ++编写的遗留应用程序。它时不时地崩溃,Valgrind告诉我它是对某个对象的双重删除。

找出导致您无法完全理解的应用程序中的双重删除以及哪个太大而无法重写的错误的最佳方法是什么?

请分享您的最佳提示和技巧!

6 个答案:

答案 0 :(得分:4)

以下是一些在这种情况下帮助我的一般性建议:

  1. 如果您使用的是记录器,请将日志记录级别调高为完全调试。在输出中查找可疑内容。如果你的应用程序没有记录对象/类的指针分配和删除,那么是时候在代码中插入一些cout << "class Foo constructed, ptr= " << this << endl;语句(以及相应的delete /析构函数打印)。
  2. 使用--db-attach = yes运行valgrind。我发现这非常方便,如果有点单调乏味。每次检测到重大内存错误或事件时,Valgrind都会向您显示堆栈跟踪,然后询问您是否要调试它。如果您的应用程序很大,您可能会发现自己反复多次按'n',但要继续查找有问题的对象首先(其次)被删除的代码行。
  3. 只是搜索代码。寻找有问题的对象的构造/删除。可悲的是,有时它最终会出现在第三方图书馆: - (。
  4. 更新:最近刚发现:显然gcc 4.8及更高版本(如果你可以在你的系统上使用GCC)有一些新的内置功能来检测内存错误,“{{3 }}”。也可在address sanitizer
  5. 中找到

答案 1 :(得分:2)

是的。 @OliCharlesworth说的话。没有万无一失的方法来测试指针,看它是否指向已分配的内存,因为它本身就只是内存位置。

您的问题所暗示的最大问题是缺乏可重复性。继续考虑到这一点,您仍然坚持将简单的“删除”结构更改为delete foo;foo = NULL;

即便如此,最好的情况是“它似乎发生的更少”,直到你真的盖章了。

我还会问Valgrind有什么证据表明这是一个双重删除问题。可能是一个更好的线索在那里挥之不去。

这是一个更简单的真正令人讨厌的问题。

答案 2 :(得分:2)

这可能适用于您,也可能不适合您。

很久以前,我正在研究当时15岁的1M +线路计划。面对完全相同的问题 - 用大量数据集双删除。有了这样的数据,任何开箱即用的“内存分析器”都是不行的。

我身边的事情:

  1. 它非常可重复 - 我们有宏语言并且每次都以相同的方式运行相同的脚本
  2. 在项目历史的某个时候,某人决定“#define malloc my_malloc”和“#define free my_free”有一些用处。这些没有比调用内置的malloc()和free()更多,但项目已经编译并以这种方式工作。
  3. 现在的诀窍/想法:

    my_malloc(int size)
    {
       static int allocation_num = 0;  // it was single threaded
    
       void* p = builtin_malloc(size+16);
    
       *(int*)p = ++allocation_num;
       *((char*)p+sizeof(int)) = 0; // not freed
    
       return (char*)p+16;  // check for NULL in order here
    }
    
    my_free(void* p)
    {
        if (*((char*)p+sizeof(int)))
        {
            // this is double free, check allocation_number
            // then rerun app with this in my_alloc
            //    if (alloc_num == XXX) debug_break();
        }
    
        *((char*)p+sizeof(int)) = 1; // freed
    
        //built_in_free((char*)p-16);  // do not do this until problem is figured out
    }
    

    使用new / delete可能会更棘手,但仍然使用LD_PRELOAD,您甚至可以在不重新编译应用程序的情况下替换malloc / free。

答案 3 :(得分:0)

您可能正在从处理删除的版本升级到新版本。

可能是以前的版本在调用delete时执行的操作,它对if (X != NULL){ delete X; X = NULL;}执行了静态检查,然后在新版本中执行delete操作。

您可能需要检查指针分配,并跟踪从构造到删除的对象名称的引用。

答案 4 :(得分:0)

我发现这很有用:backtrace() on linux。 (你必须使用-rdynamic进行编译。)这可以通过在所有内存操作(new / delete)周围放置一个try / catch块然后在catch块中找出双重释放的来源,打印出堆栈跟踪。

通过这种方式,您可以比运行valgrind更快地缩小嫌疑人范围。

我在一个方便的小课程中包裹了回溯,以便我可以说:

try {
  ...
} catch (...) {
  StackTrace trace;
  std::cerr << "Double free!!!\n" << trace << std::endl;
  throw;
} 

答案 5 :(得分:0)

在Windows上,假设应用程序是使用MSVC ++构建的,您可以利用标准库调试版本中内置的大量heap debugging工具。

同样在Windows上,您可以使用Application Verifier。如果我没记错的话,它有一种模式,强制每个分配到一个单独的页面,其间有受保护的保护页面。它在查找缓冲区溢出方面非常有效,但我怀疑它对双重自由情况也很有用。

你可以做的另一件事(在任何平台上)都是制作转换源(可能带有宏)的副本,以便每个实例:

delete foo;

替换为:

{ delete foo; foo = nullptr; }

(大括号在很多情况下都有帮助,虽然它并不完美。)这会将许多双自由实例转换为空指针引用,使其更易于检测。它并没有抓住一切;你可能有一个陈旧指针的副本,但它可以帮助压缩许多常见的使用后删除方案。