标准C++未提供malloc_count等接口是因为标准库不暴露堆分配统计API,底层依赖libc/CRT且默认禁用计数;Linux可用LD_PRELOAD拦截malloc/free,Windows可借助MSVC的_malloc_hook(仅Debug模式),但需注意线程安全与泄漏判定局限。

为什么标准 C++ 没有内置的 malloc_count 或 heap_allocs_total
因为 C++ 标准库不暴露底层堆分配器的统计接口,new/delete 和 malloc/free 调用最终落到 libc(如 glibc)或 CRT(如 MSVCRT),而这些实现默认关闭运行时计数——不是“做不到”,而是“不提供公开 API”。直接读取内部计数器(比如 glibc 的 __malloc_stats)不稳定、非可移植、且可能被优化掉。
用 LD_PRELOAD 拦截 malloc/free/realloc 最可靠
这是 Linux 下最实用、侵入性最小的方式:不改源码、不重编译、不依赖调试符号。核心是写一个共享库,导出同名函数,内部计数并调用真实函数。
示例关键逻辑:
extern "C" {
static size_t alloc_count = 0;
static size_t free_count = 0;
<p>void<em> malloc(size_t size) {
static void</em> (<em>real_malloc)(size_t) = nullptr;
if (!real_malloc) real_malloc = dlsym(RTLD_NEXT, "malloc");
void</em> p = real_malloc(size);
if (p) alloc_count++;
return p;
}</p><p>void free(void<em> ptr) {
static void (</em>real_free)(void*) = nullptr;
if (!real_free) real_free = dlsym(RTLD_NEXT, "free");
if (ptr) free_count++;
real_free(ptr);
}
}编译后用 LD_PRELOAD=./libhook.so ./your_program 运行,最后通过全局变量或信号(如 SIGUSR1)触发打印 alloc_count - free_count 即当前未释放的分配次数(注意:不是字节数,是调用次数)。
立即学习“C++免费学习笔记(深入)”;
- 必须用
extern "C"防止 name mangling -
dlsym(RTLD_NEXT, ...)是关键,否则会无限递归调用自己 - 不要在
malloc失败时计数(p == nullptr),否则差异值失真 - 忽略
realloc的特殊情况:它可能内部复用内存,但按规范仍算一次新分配 + 一次释放,建议统一计为“一次 alloc + 一次 free”再加一次 alloc(视实现而定)
Windows 下用 MSVC 的 _malloc_hook 和 _free_hook
MSVC CRT 提供了官方钩子,但仅限 Debug 模式(_DEBUG 定义下),且从 VS2015 起已被标记为 deprecated,但仍可用。
启用方式:
_set_malloc_hook(my_malloc_hook);
_set_free_hook(my_free_hook);
<p>int my_malloc_hook(size_t size, int type) {
if (type == _HOOK_ALLOC) alloc_count++;
return 1; // 允许分配
}</p><p>int my_free_hook(void* ptr, int type) {
if (type == _HOOK_FREE) free_count++;
return 1;
}-
type参数区分_HOOK_ALLOC、_HOOK_FREE、_HOOK_REALLOC,避免误计 - Release 模式下这些钩子完全无效,需改用 Detours 库或 ETW 事件捕获
- 多线程下必须加锁(
std::atomic或互斥量),否则计数竞争
别混淆“分配次数差”和“内存泄漏”
alloc_count - free_count 只反映动态分配调用的净次数,不是内存是否泄漏的充分证据。常见干扰项:
- 全局对象的
new在main前执行,delete在main后执行,程序退出时 CRT 自动清理,但你的钩子仍会计数 - 第三方库(如 Boost、Qt)内部缓存大量小块内存,长期持有不释放,属于设计行为,不是 bug
-
std::string、std::vector的 capacity 扩容可能导致多次malloc,但只在 resize 时才真正free旧缓冲区 - ASLR 或地址空间布局变化会让同一段代码在不同运行中产生不同计数,不能单次结果定论
真正要定位泄漏,得结合分配栈回溯(如 backtrace() 或 __builtin_frame_address),而不仅是总数差。


















