在内部,所有 NSS 接口(其中getaddrinfo
一个)看起来像gethostbyname_r
:
int gethostbyname_r(const char *name,
struct hostent *ret, char *buf, size_t buflen,
struct hostent **result, int *h_errnop);
调用者通过buf
,buflen
字节为结果数据提供一个缓冲区。如果事实证明此缓冲区的大小不足,则该函数将失败并出现ERANGE
错误。调用者应该增长缓冲区(以某种方式重新分配它)并调用函数,其他参数相同。这会重复,直到缓冲区足够大并且函数成功(或函数由于其他原因而失败)。我们如何最终得到这个奇怪的界面是一个较长的故事,但它是我们今天拥有的界面。看起来不同,但内部支持实现与公共函数getaddrinfo
非常相似。gethostbyname_r
因为 retry-with-a-larger-buffer 习惯用法在整个 NSS 代码中很常见,所以struct scratch_buffer
引入了。(以前,固定缓冲区大小的组合相当不拘一格alloca
,alloca
带有malloc
后备,等等。)struct scratch_buffer
组合了一个固定大小的堆栈缓冲区,用于第一个 NSS 调用。如果调用失败ERANGE
,scratch_buffer_grow
则调用 , 切换到堆缓冲区,并在后续调用中分配更大的堆缓冲区。scratch_buffer_free
如果有,则释放堆缓冲区。
在您的示例中,tcmalloc
报告的泄漏与暂存缓冲区无关。(我们在 中肯定有这样的错误getaddrinfo
,特别是在晦涩的错误路径上,但当前代码应该基本没问题。)链接顺序也不是问题,因为显然tcmalloc
它是活动的,否则您将不会收到任何泄漏报告。
您看到泄漏tcmalloc
(但没有使用其他工具,例如 valgrind)的原因是tcmalloc
它没有调用__libc_freeres
专门为堆检查器添加的魔术函数。通常,当进程终止时,glibc 不会释放所有内部分配,因为内核无论如何都会释放该内存。大多数子系统以某种方式在__libc_freeres
. 在getaddrinfo
示例中,我看到以下仍然分配的资源:
- 解析结果
/etc/resolv.conf
(系统 DNS 配置)。
- 解析结果
/etc/nsswitch.conf
(NSS 配置)。
dlopen
内部调用产生的各种动态加载器数据结构(用于加载 NSS 服务模块。
- 用于记录系统 IPv4/IPv6 支持的缓存
getaddrinfo
。
如果您在 valgrind 下运行示例,您可以使用如下命令轻松查看这些分配:
valgrind --leak-check=full --show-reachable=yes --run-libc-freeres=no
关键部分是--run-libc-freeres=no
,它指示 valgrind不要调用__libc_freeres
,默认情况下它会这样做。如果省略此参数,valgrind 将不会报告任何内存泄漏。