我正在维护一个用 C++ 编写的遗留应用程序。它时不时地崩溃,Valgrind 告诉我它对某个对象的双重删除。
在您不完全理解并且太大而无法重写的应用程序中找到导致双重删除的错误的最佳方法是什么?
请分享您的最佳提示和技巧!
我正在维护一个用 C++ 编写的遗留应用程序。它时不时地崩溃,Valgrind 告诉我它对某个对象的双重删除。
在您不完全理解并且太大而无法重写的应用程序中找到导致双重删除的错误的最佳方法是什么?
请分享您的最佳提示和技巧!
以下是一些在这种情况下对我有帮助的一般性建议:
cout << "class Foo constructed, ptr= " << this << endl;
在您的代码中插入一些语句(以及相应的delete
/destructor 打印)。是的。@OliCharlesworth 说了什么。没有万无一失的方法来测试指针以查看它是否指向已分配的内存,因为它实际上只是内存位置本身。
您的问题暗示的最大问题是缺乏可重复性。考虑到这一点,您将无法将简单的“删除”结构更改为delete foo;foo = NULL;
.
即使那样,最好的情况也是“它似乎发生得更少”,直到你真正把它压下来。
我还想问 Valgrind 有什么证据表明这是一个双重删除问题。可能是一个更好的线索在那里徘徊。
这是更简单的真正令人讨厌的问题之一。
这可能适合您,也可能不适合您。
很久以前,我正在开发 1M+ 行的程序,当时只有 15 岁。面临完全相同的问题 - 使用庞大的数据集进行双重删除。有了这样的数据,任何开箱即用的“内存分析器”都是不行的。
我身边的事情:
现在的技巧/想法:
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,甚至无需重新编译您的应用程序。
您可能是从一个处理 delete 的版本与新版本不同的版本升级。
可能以前的版本所做的是在delete
被调用时进行静态检查if (X != NULL){ delete X; X = NULL;}
,然后在新版本中它只是执行delete
操作。
您可能需要检查指针分配,并从构造到删除跟踪对象名称的引用。
我发现这很有用:backtrace() on linux。(您必须使用 -rdynamic 进行编译。)这可以让您通过在所有内存操作(新/删除)周围放置一个 try/catch 块然后在 catch 块中打印出您的堆栈跟踪来找出双重释放的来源。
这样,您可以比运行 valgrind 更快地缩小嫌疑人范围。
我将 backtrace 包装在一个方便的小类中,这样我就可以说:
try {
...
} catch (...) {
StackTrace trace;
std::cerr << "Double free!!!\n" << trace << std::endl;
throw;
}
在 Windows 上,假设应用程序是使用 MSVC++ 构建的,您可以利用标准库调试版本中内置的大量堆调试工具。
同样在 Windows 上,您可以使用Application Verifier。如果我没记错的话,它有一种模式可以将每个分配强制到一个单独的页面上,中间有受保护的保护页面。它在查找缓冲区溢出方面非常有效,但我怀疑它对于双重释放情况也很有用。
您可以(在任何平台上)做的另一件事是制作已转换的源的副本(可能使用宏),以便每个实例:
delete foo;
替换为:
{ delete foo; foo = nullptr; }
(大括号在许多情况下都有帮助,尽管它并不完美。)这会将许多 double-free 实例转换为空指针引用,从而更容易检测到。它并不能捕获所有内容。您可能有一个过时指针的副本,但它可以帮助压缩许多常见的删除后使用场景。