最直接的方式是利用malloc_hook和free_hook插入自定义逻辑,但该方法已被glibc 2.34标记为deprecated;需保存原钩子、加锁防竞争、避免递归调用,并严格匹配函数签名。

如何用 malloc_hook 获取实时堆分配统计
Linux 下最直接的方式是利用 malloc_hook 和 free_hook,它们允许你在每次 malloc/free 调用时插入自定义逻辑。但注意:这是 GNU libc 的非标准扩展,仅在 glibc 中可用,且从 glibc 2.34 开始被标记为 deprecated(不推荐使用),新项目应优先考虑 mallopt + mallinfo 或更现代的替代方案。
实际操作中,你需要:
- 在程序启动前(如
main入口前)保存原始 hook 函数指针,并设置自己的钩子 - 在钩子函数中加锁(
pthread_mutex_lock),避免多线程下计数竞争 - 注意递归调用风险——你的钩子里若触发了 malloc(比如
std::string构造),会再次进入钩子,必须用 TLS 或静态 flag 拦截 - 钩子函数签名必须严格匹配:
void* (*__malloc_hook)(size_t, const void*),第二个参数是调用地址(可用于过滤系统库内部调用)
mallinfo 与 mallinfo2 的区别和精度问题
mallinfo 返回的是粗粒度的堆状态快照,字段如 arena(主分配区大小)、uordblks(已分配字节数)等,但它在 glibc 2.33+ 中已被标记为 obsolete;mallinfo2 是其替代品,返回 struct mallinfo2,字段更全、无符号、能反映多 arena 场景(如多线程下每个线程可能有自己的 malloc arena)。
关键差异点:
立即学习“C++免费学习笔记(深入)”;
-
mallinfo的uordblks可能低估真实使用量——它只统计当前“已分配但未释放”的块,不包含 mmap 分配的大块(>128KB 默认走 mmap) -
mallinfo2新增hblkhd(mmap 占用字节数)和usmblks(已调用malloc_trim但尚未归还给 OS 的内存),更适合评估真实内存压力 - 两者都**不区分用户代码 vs 标准库内部分配**(比如
std::regex构建 DFA 时的 malloc),统计结果是整个进程堆视图
为什么 LD_PRELOAD 替换 malloc 更可靠但更重
当需要精确追踪所有堆操作(包括第三方库、STL 容器、甚至 new 运算符底层调用),malloc_hook 不够用——因为 new 可能直接调用 __libc_malloc 绕过 hook,而某些优化后的 STL 实现还会缓存小对象。
此时可写一个轻量级 malloc 替换库,导出 malloc/free/realloc/memalign 符号,用 LD_PRELOAD 注入:
- 必须同时替换
operator new和operator delete(否则 C++ new/delete 不会被捕获) - 调用原始 libc malloc 时,要用
dlsym(RTLD_NEXT, "malloc"),不能直接调用malloc,否则无限递归 - 记录分配栈回溯需用
backtrace+backtrace_symbols,但要注意符号解析开销大,建议只在 debug 模式启用 - 注意
LD_PRELOAD对static链接的二进制无效,且无法拦截mmap/brk系统调用本身
std::allocator 和自定义分配器的实际影响
如果你用了自定义 std::allocator(比如内存池或 slab 分配器),那么 mallinfo 和 malloc_hook 都不会统计这部分内存——因为它们完全绕过了 libc malloc。这种情况下,统计必须由分配器自身暴露接口,例如:
- 在 allocator 的
allocate/deallocate成员函数里累加/减计数器 - 通过 RAII guard 在构造/析构时更新全局统计变量(需保证线程安全)
- 若用
std::pmr::polymorphic_allocator,则需确保 underlying resource(如std::pmr::monotonic_buffer_resource)的生命周期可控,并在其析构时上报峰值用量
真正容易被忽略的是:C++ 标准库容器(如 std::vector)在 resize/rehash 时可能多次 allocate/deallocate,而这些中间状态不会体现在最终的 mallinfo2.uordblks 里——你看到的只是当前快照,不是历史峰值或累计总量。


















