为什么valgrind报告glibc tsearch()随机泄漏内存?

为什么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>

我正在使用glibcapi系列在一个示例程序中存储动态分配的数据块

我发现,当我使用
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函数不需要释放内存来保存所需的性能引用。