内存快照是记录堆上活跃对象的布局与关键状态,包括malloc/new分配的地址、大小及释放状态;它通过拦截内存操作并序列化日志实现,而非复制整个地址空间。

什么是内存快照,C++里它到底在记什么
内存快照不是复制整个进程地址空间,而是记录当前堆上活跃对象的布局和关键状态——比如 malloc / new 分配的地址、大小、是否已释放。真正能落地的“简单快照”,核心是拦截分配/释放行为,并在触发点(如信号、函数调用)把快照数据序列化到文件或内存缓冲区。
别指望靠 memcpy 一段虚拟地址就能还原对象——没有元信息(类型、生命周期、指针关系),快照只是字节垃圾。
用 malloc_hook 拦截分配行为(Linux glibc 环境)
__malloc_hook 和 __free_hook 是 glibc 提供的钩子,但注意:它们在较新版本(glibc 2.34+)已被移除,且线程不安全,仅适合单线程调试场景。
- 必须在程序启动早期(main 之前或刚进 main)设置钩子,否则部分系统分配(如 stdio 初始化)会绕过
- 钩子函数里不能调用任何可能再触发 malloc 的函数(比如
printf、std::string构造),否则栈溢出或死循环 - 推荐只做轻量记录:存
void*地址、size_t大小、分配时序号,用write()直接写二进制到 fd,避开 stdio
示例片段:
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
立即学习“C++免费学习笔记(深入)”;
static void* my_malloc_hook(size_t size, const void* caller) {
void* ptr = __libc_malloc(size);
if (ptr) {
// 记录到全局环形缓冲区(避免 malloc)
snapshot_log[log_idx].addr = ptr;
snapshot_log[log_idx].size = size;
snapshot_log[log_idx].type = 'A';
log_idx = (log_idx + 1) % LOG_SIZE;
}
return ptr;
}Windows 上用 _CrtSetAllocHook 替代
Windows CRT 提供 _CrtSetAllocHook,仅对 malloc/calloc/realloc 有效,对 new 无效——所以必须重载全局 operator new 才能覆盖 C++ 对象分配。
-
_CrtSetAllocHook回调参数包含nBlockUse(区分 _NORMAL_BLOCK / _CLIENT_BLOCK),可过滤掉 CRT 内部块 - 回调中调用
_CrtDbgReport不安全;推荐写入内存映射文件或预分配日志缓冲区 - 注意:Debug 模式下 CRT 默认开启堆检查,Release 模式需手动链接 debug 库或改用 HeapWalk API
快照导出后怎么查泄漏或对比差异
导出的快照本质是地址-大小表,要定位泄漏,得做两次快照(起始 + 结束),然后用地址做差集:
- 按地址排序后双指针扫描,找出只在第一次出现、未在第二次被
free的块 - 若记录了调用栈(需
backtrace或 WindowsCaptureStackBackTrace),可定位到具体源码行——但开销大,慎用于生产 - 别直接 memcmp 两份快照二进制:地址随机化(ASLR)会导致相同分配产生不同地址,应比对 size + 分配序号 + 调用栈哈希
最常被忽略的一点:快照本身也占用堆内存。所有日志结构必须静态分配或 mmap 预留,否则快照逻辑会干扰被观测对象的内存行为。

















