为什么valgrind报告glibc tsearch()随机泄漏内存?
我正在使用glibcapi系列在一个示例程序中存储动态分配的数据块 我发现,当我使用为什么valgrind报告glibc tsearch()随机泄漏内存?,c,valgrind,glibc,C,Valgrind,Glibc,我正在使用glibcapi系列在一个示例程序中存储动态分配的数据块 我发现,当我使用tsearch()向树中添加几个malloc()ed块时,valgrind会将其中一些块报告为“可能丢失”。虽然“可能丢失”与“肯定丢失”并不完全相同,但通常的做法是调查造成这些损失的原因 我的示例程序如下所示: #include <stdio.h> #include <search.h> #include <stdlib.h> #include <signal.h>
tsearch()
向树中添加几个malloc()
ed块时,valgrind会将其中一些块报告为“可能丢失”。虽然“可能丢失”与“肯定丢失”并不完全相同,但通常的做法是调查造成这些损失的原因
我的示例程序如下所示:
#include <stdio.h>
#include <search.h>
#include <stdlib.h>
#include <signal.h>
struct data {
int id;
char *str;
};
static int
compare (const void *a, const void *b) {
const struct data *data_a = a, *data_b = b;
if (data_a->id < data_b->id) {
return -1;
} else if (data_a->id > data_b->id) {
return 1;
} else {
return 0;
}
}
int main (int argc, char **argv) {
void *tree = NULL;
struct data *d1, *d2, *d3, *d4;
d1 = malloc(sizeof(struct data));
d1->id = 10;
d1->str = "Hello";
tsearch(d1, &tree, compare);
d2 = malloc(sizeof(struct data));
d2->id = 30;
d2->str = "Goodbye";
tsearch(d2, &tree, compare);
d3 = malloc(sizeof(struct data));
d3->id = 20;
d3->str = "Thanks";
tsearch(d3, &tree, compare);
d4 = malloc(sizeof(struct data));
d4->id = 40;
d4->str = "OK";
tsearch(d4, &tree, compare);
raise(SIGINT);
return 0;
}
$ gcc ts.c -o ts
$ valgrind --leak-check=full ./ts
==2091== Memcheck, a memory error detector
==2091== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==2091== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==2091== Command: ./ts
==2091==
==2091==
==2091== Process terminating with default action of signal 2 (SIGINT)
==2091== at 0x4E7AE97: raise (raise.c:51)
==2091== by 0x1088CE: main (in /home/ubuntu/ts)
==2091==
==2091== HEAP SUMMARY:
==2091== in use at exit: 160 bytes in 8 blocks
==2091== total heap usage: 8 allocs, 0 frees, 160 bytes allocated
==2091==
==2091== 24 bytes in 1 blocks are possibly lost in loss record 8 of 8
==2091== at 0x4C2FB0F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==2091== by 0x4F59483: tsearch (tsearch.c:338)
==2091== by 0x108801: main (in /home/ubuntu/ts)
==2091==
==2091== LEAK SUMMARY:
==2091== definitely lost: 0 bytes in 0 blocks
==2091== indirectly lost: 0 bytes in 0 blocks
==2091== possibly lost: 24 bytes in 1 blocks
==2091== still reachable: 136 bytes in 7 blocks
==2091== suppressed: 0 bytes in 0 blocks
==2091== Reachable blocks (those to which a pointer was found) are not shown.
==2091== To see them, rerun with: --leak-check=full --show-leak-kinds=all
==2091==
==2091== For counts of detected and suppressed errors, rerun with: -v
==2091== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
$
Valgrind报告一个24字节的块丢失。如果我将raise(SIGINT)
移动到d4
分配和树添加之前,则不会报告任何块丢失
为什么添加4个块时会丢失一个块,即使添加3个块时一个也不会丢失?事实证明,glibc
tsearch()
实现有点顽皮,它可以在指针中旋转低阶位,指向它存储在二元搜索树中的块。它使用指针中的低位存储标志:
具体而言,实现使用这些宏设置或清除低位指针位,以分别将块标记为红色或黑色:
#define SETRED(N) (N)->left_node |= ((uintptr_t) 0x1)
#define SETBLACK(N) (N)->left_node &= ~((uintptr_t) 0x1)
指针被有效地递增,这使valgrind认为这些有效指针(存储在tsearch()
树中)已被删除。但这些不是丢失的块-它们成功地存储在二进制搜索树中,对低阶位进行模化tsearch()
将在访问树时对这些位进行必要的屏蔽。tsearch()可以这样做,因为malloc()
ed块通常至少与偶数地址对齐
只有在二叉树中标记为“红色”节点的块才具有此位集,因此其模式是否“可能丢失”完全取决于实现在添加、删除和重新平衡操作期间如何将块分类为“红色”或“黑色”
因此
tsearch()
s位旋转使valgrind错误地认为这些块丢失了。在这种情况下,valgrind报告的是误报。奇怪,但我不同意你在这里的措辞valgrind清楚地说“可能丢失”,所以它只是可能的,它从来没有说它是确定的,所以valgrind在这里不是不正确的。@Stargateur很公平,但建议“可能丢失”通常应被视为“丢失”。好吧,你链接到quote valgrind doc的问题是“可能丢失,或“可疑”,显然是这样,这个实现做了一些真正不安全的事情,并且完全实现了行为。您不应该像那样使用指针,但它们可以,因为它们是实现(glibc)。尽管如此,valgrind是对的,这个指针已经不存在了,指针已经“移动”了一点,所以valgrind报告可能是因为指针仍然指向接近原始地址的东西。同样是这样,所以我不明白为什么valgrind在这里被认为是一个bug。@Stargateur如果你在这篇文章中发现这是一个valgrind bug,那么这不是我的意图。“可能丢失”不是“丢失”。在C语言中,您可以对指针的表示形式执行任何可逆转换,甚至可以对指针进行加密并将其上载到云中,然后下载并解密,而不会“丢失”指向的对象。这里唯一的漏洞是(特定于实现的)知道|1
在malloc
返回的指针上是可逆的。一些libc函数不需要释放内存来节省性能。这意味着要注意保持他们使用的少量间隔。valgrind报告此内存泄漏,但它会在应用程序退出时返回。@Serge某些libc函数不需要释放内存来保存所需的性能引用。