我在一个大项目中使用tcmalloc已经有几个月了,到目前为止我必须说我很高兴,最重要的是它的HeapProfiling功能允许跟踪内存泄漏并删除它们。
在过去的几周里,虽然我们的应用程序遇到了随机崩溃,但我们无法找到随机崩溃的来源。在一个非常特殊的情况下,当应用程序崩溃时,我们发现自己的应用程序线程之一的堆栈已完全损坏。好几次,我发现线程卡在tcmalloc :: PageHeap :: AllocLarge()中,但由于我没有连接tcmalloc的调试符号,我无法理解问题是什么。
经过近一周的调查,今天我尝试了最简单的事情:从联动中删除tcmalloc以避免使用它,只是为了看看发生了什么。嗯......我终于找到了问题所在,而且违规代码看起来非常像这样:
void AllocatingFunction()
{
Object object_on_stack;
ProcessObject(&object_on_stack);
}
void ProcessObject(Object* object)
{
...
// Do Whatever
...
delete object;
}
使用libc,应用程序仍然崩溃,但我终于看到我在堆栈上分配的对象上调用了delete。
我仍然无法弄清楚为什么tcmalloc会保持应用程序运行而不管这种非常危险(如果不是完全错误的)对象释放,以及当AllocatingFunction结束时object_on_stack超出范围时的双重释放。事实是,可以反复调用违规代码而不暗示潜在的憎恶。
我知道内存释放是未正确使用时的“未定义行为”之一,但令我惊讶的是“标准”libc和tcmalloc之间存在这种不同的行为。
有没有人对tcmalloc保持应用程序运行的原因有什么见解?
提前致谢:)
度过美好的一天
答案 0 :(得分:2)
非常危险(如果不是完全错误的)对象释放
嗯,我在这里不同意, 是完全错误的,因为你调用UB,任何事情都可能发生。
这在很大程度上取决于tcmalloc代码在解除分配时的实际作用,以及它如何在该位置使用堆栈周围的(可能是垃圾)数据。
我也看到过tcmalloc在这种情况下崩溃,以及glibc进入无限循环。你看到的只是巧合。
答案 1 :(得分:0)
首先,你的案例中没有双free
。当object_on_stack超出范围时,没有free
调用,只是堆栈指针减少(或者更确切地说,随着堆栈增长......)。
其次,在删除期间,TcMalloc应该能够识别堆栈中的地址不属于程序堆。以下是free(ptr)
实施的一部分:
const PageID p = reinterpret_cast<uintptr_t>(ptr) >> kPageShift;
Span* span = NULL;
size_t cl = Static::pageheap()->GetSizeClassIfCached(p);
if (cl == 0) {
span = Static::pageheap()->GetDescriptor(p);
if (!span) {
// span can be NULL because the pointer passed in is invalid
// (not something returned by malloc or friends), or because the
// pointer was allocated with some other allocator besides
// tcmalloc. The latter can happen if tcmalloc is linked in via
// a dynamic library, but is not listed last on the link line.
// In that case, libraries after it on the link line will
// allocate with libc malloc, but free with tcmalloc's free.
(*invalid_free_fn)(ptr); // Decide how to handle the bad free request
return;
}
调用invalid_free_fn崩溃。