商城首页欢迎来到中国正版软件门户

您的位置: 首页 > 文章列表 > 编程开发 > C++如何获取当前程序的堆分配总量、当前驻留量与释放统计

C++如何获取当前程序的堆分配总量、当前驻留量与释放统计

  发布于2026-07-15 阅读(0)

扫一扫,手机访问

Linux下用/proc/self/status读取堆内存快照

Linux进程的堆使用情况,标准C++ API是给不了的。不过内核很贴心,在`/proc/self/status`这个虚拟文件里曝光了不少近似指标。 关键字段有两个:`VmRSS`——当前程序到底占用了多少物理内存;`VmSize`——整个虚拟地址空间的大小。你可能会想,有没有`HeapAlloc`这样的字段直接告诉我堆分配总量?很可惜,默认的status文件里没有这个。实际能稳定读取的是`VmData`(包含了已初始化数据段和堆的估算值)和`VmStk`(栈)。但`VmData`有个大问题:它会把全局变量、BSS段都算进去,所以不能单纯拿它当`malloc`的分配量来看。 更靠谱的做法,是去`/proc/self/maps`里找那个标记为`[heap]`的区域。这个东西呢,通常只有一行,起始地址和结束地址相减,就能算出当前堆的虚拟大小。但它不分什么已分配、已释放还是未使用,它反映的仅仅是`brk`系统调用当前的上限。要算驻留量,还是得用`VmRSS`减去其他段来估算,不过误差通常能控制在几MB内,对于大多数诊断场景来说够用了。 示例代码是这样的,直接用C++的文件读取和正则表达式就能搞定:
#include #include #include 

```cpp size_t get_heap_size_kb() { std::ifstream f("/proc/self/maps"); std::string line; while (std::getline(f, line)) { if (line.find("[heap]") != std::string::npos) { std::regex r(R"(^([0-9A-Fa-f]+)-([0-9A-Fa-f]+)\s+)"); std::smatch m; if (std::regex_search(line, m, r)) { auto start = std::stoul(m[1].str(), nullptr, 16); auto end = std::stoul(m[2].str(), nullptr, 16); return (end - start) / 1024; } } } return 0; } ```

Windows下用GetProcessHeap + HeapWalk遍历堆块

Windows平台提供了一套更底层的堆枚举能力,但使用起来要格外小心。默认的进程堆(`GetProcessHeap()`)只覆盖了`HeapAlloc`和`LocalAlloc`这些Windows API的调用,并不包含C运行时库(CRT)的`malloc`或者C++的`new`。如果你的程序混用了CRT和WinAPI的内存函数,用这种方法统计出来的结果会缺胳膊少腿。 想用`HeapWalk`,得满足几个苛刻的条件: * 调用前必须给堆上锁(`HeapLock(h)`),否则会死锁或直接返回失败。 * 每次调用会返回一个`PROCESS_HEAP_ENTRY`结构。只有`dwFlags`包含`PROCESS_HEAP_ENTRY_BUSY`时,才表示这是一个已分配的块。`cbData`字段就是该块的字节数,累加起来就是当前的分配总量。 * 它只统计当前正在使用的块,已经释放的(空闲)不计入。当然,你也别指望能从它那里知道历史上释放了多少次。 最大的限制在于:`HeapWalk`是不可重入的。在多线程环境下,你必须暂停其他所有线程,或者确保在遍历期间没有任何堆操作发生。此外,CRT的`malloc`默认用的是私有堆,要想被`HeapWalk`捕获,得换成`_malloc_dbg`并开启调试堆才行。

跨平台方案:LD_PRELOAD拦截或MSVC调试堆钩子

想要精确统计每一次`malloc`和`free`调用的次数和字节数,唯一靠谱的办法就是“拦截”——把内存分配函数给劫持了。Linux上可以用`LD_PRELOAD`注入一个自定义的`malloc`实现;Windows上则可以用MSVC提供的`_CrtSetAllocHook`。 立即学习“C++免费学习笔记(深入)”; Linux下的实现要点: * 需要实现`malloc`、`free`、`realloc`、`calloc`四个核心符号。在内部,它们最终会调用`__libc_malloc`这些真实的函数来完成实际分配。 * 用一个全局的原子计数器,来记录总分配量、当前驻留量以及释放次数。 * 注意几个细节:`free(nullptr)`是允许的,必须正确处理;`realloc`在逻辑上可能等价于`malloc` + `memcpy` + `free`,需要拆解开来计数。 * 编译这个动态库时,需要加上`-fPIC -shared`选项。运行时,通过`LD_PRELOAD=./libhook.so ./your_program`来加载。 Windows的调试堆钩子: * 这个东西只在Debug模式下生效,Release版本下是没有`_CrtSetAllocHook`的。 * 钩子函数接收一个`int nAllocType`参数,用来区分是`_HOOK_ALLOC`分配还是`_HOOK_FREE`释放。 * 你可以在钩子里记录每次调用的地址、大小,甚至用`CaptureStackBackTrace`抓取调用栈,对于定位内存泄漏简直是神器。 * 但它的覆盖范围有限,无法捕获非CRT的分配,比如DirectX或者OpenSSL内部自己做的`malloc`。

为什么mallinfomalloc_stats不推荐用于生产环境

最后说说`mallinfo`和`malloc_stats`这两个看起来很方便的东西。说实话,它们的水很深,不推荐用在生产环境里。 `mallinfo`在glibc 2.33之后已经被标记为废弃了。它返回的`arena`、`ordblks`这些字段,语义越来越模糊,而且不支持多个arena的场景——现代glibc为了提高并发性能,默认会启用多个堆arena。你调用它拿到的`usmblks`(可分配空间)经常会远大于`malloc`的实际用量,而`uordblks`(已分配)又不包含通过`mmap`分配的大块内存。 至于`malloc_stats()`,它直接把结果输出到stderr,没法结构化解析。而且它的输出格式会随着glibc版本变化,有的版本显示“max mmap regions”,有的就不显示。“system bytes”包含了所有`sbrk`和`mmap`的申请量,但“in use bytes”只统计当前繁忙的块,并不等于程序实际驻留的物理内存。在多线程下,这些统计值甚至可能出现瞬间不一致的情况,因为它没有原子性保证。 所以,当你真正需要一个精确的指标时,别图省事去依赖这些接口。要么老老实实走`/proc`或WinAPI做底层枚举,要么自己动手写挂钩——后者的工程量虽大,但拿到的数据是可信的。 记住一个核心概念:堆总量 ≠ 驻留量 ≠ 逻辑使用量。这三者之间的差值,取决于操作系统的页回收策略、内存碎片、分配器的延迟合并等复杂因素。同一个时刻,这三个数值可能差了好几倍。测的时候,记得关掉其他干扰进程,多次采样看趋势,别对单次读数太较真。
本文转载于:https://www.php.cn/faq/2822281.html 如有侵犯,请联系zhengruancom@outlook.com删除。
免责声明:正软商城发布此文仅为传递信息,不代表正软商城认同其观点或证实其描述。

热门关注