linux下/proc/self/maps是获取进程虚拟内存布局快照的零开销唯一方法,格式固定为start-end perm offset dev inode pathname,内核动态生成,无需root权限,字段稳定、覆盖全面;windows无等价接口,必须用virtualqueryex遍历地址空间,开销大、易漏页且无法直接获取符号信息。

Linux 下直接读 /proc/self/maps 是最轻量、最可靠的方式;Windows 没有等价接口,必须用 VirtualQueryEx 配合遍历地址空间,开销大且易漏页。
Linux:用 /proc/self/maps 一行解析出完整 VMA 列表
这是唯一能拿到「当前进程虚拟内存布局快照」的零开销方法。内核在每次读取时动态生成,包含所有 mmap 区域、堆、栈、共享库映射、VDSO 等,字段对齐稳定,无需 root 权限。
- 每一行格式固定:
start-end perm offset dev inode pathname,例如7f8b2c000000-7f8b2c021000 rw-p 00000000 00:00 0 [heap] - 注意
perm字段中p/s表示私有/共享,offset对匿名映射恒为00000000,不能用来判断是否可读 - 用
std::ifstream逐行读取即可,别用mmap映射该文件——它本质是内核 procfs 伪文件,mmap 会失败并返回EINVAL - 若需过滤掉
[stack]或[vvar]等特殊区域,正则匹配\[.*\]最稳妥,别依赖空格分割第 6 字段——有些路径含空格(如带版本号的 so)
Windows:必须用 VirtualQueryEx 遍历整个用户地址空间
Windows 没有类似 /proc/self/maps 的聚合视图,VirtualQueryEx(GetCurrentProcess(), addr, &mbi, sizeof(mbi)) 只返回单个内存块信息,必须从 0x10000 开始按 mbi.RegionSize 步进扫描,直到 addr == 0(32 位)或超出用户空间上限(64 位通常到 0x7FFFFFFE0000)。
- 起始地址不能设为
NULL,否则第一次调用返回ERROR_INVALID_PARAMETER;推荐用uintptr_t addr = 0x10000; - 每次调用后,下一轮
addr += mbi.RegionSize;,不是addr += PAGE_SIZE——跳过未分配区域才能收敛,否则遍历几小时都完不了 -
mbi.State为MEM_FREE的区域要跳过,它们占地址空间绝大部分,但无意义;重点看MEM_COMMIT+mbi.Protect(如PAGE_READWRITE) - 无法区分「堆」和「malloc 分配的匿名映射」,因为 CRT 堆底层也是
VirtualAlloc,只能靠地址范围粗略推测(如靠近GetProcessHeap()返回值)
跨平台封装要注意的三个硬伤
强行写一个统一接口容易埋雷,尤其在调试器、内存分析工具中:
- Linux 的
/proc/self/maps包含符号信息(so 名称、偏移),Windows 的VirtualQueryEx完全不提供模块名,需额外调用EnumProcessModules+GetModuleFileNameEx补充,但仅对加载的 DLL 有效,对VirtualAlloc分配的裸内存无效 - 两者粒度不同:Linux 按 VMA(可能跨数 GB)返回,Windows 按内存页(4KB)或提交块(64KB)返回,合并逻辑复杂且易出错
- Linux 下 fork 后子进程 maps 内容独立,Windows 的
VirtualQueryEx若传入其他进程句柄,需PROCESS_QUERY_INFORMATION权限,普通进程默认拿不到——别指望用同一套代码 dump 其他进程
真正做内存分析时,/proc/self/maps 的文本解析性能远高于 Windows 的地址空间遍历,后者在 64 位进程上可能触发数万次系统调用。如果目标只是诊断本进程,Linux 用户别碰 mincore 或 proc/pagemap——那些是查物理页用的,和虚拟布局无关。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











