启用 page heap 后程序崩溃是正常现象,因它在内存前后插入不可访问页以立即捕获越界读写;需用 windbg 的 !heap -p -a 查看调用栈,确保 pdb 匹配、禁用 aslr,并注意 server 版需注册表启用。

gflags 启用 page heap 后程序直接崩溃,为什么?
page heap 默认启用的是“完整页堆”(full page heap),它会在每次 malloc 或 new 分配的内存前后插入不可访问页,一旦越界读写立刻触发 ACCESS_VIOLATION。这不是 bug,是预期行为——它把“延迟崩溃”提前成“立即崩溃”,反而帮你准确定位问题点。
常见错误现象:Application tried to read from address 0000000000123456 这类异常在 ntdll.dll 中抛出,调用栈顶端看不到你自己的代码,容易误判为系统问题。
- 只对目标进程启用:用
gflags /p /enable your_app.exe /full,别全局开,否则 VS 调试器、explorer 都可能卡死 - 确保符号路径正确:在 WinDbg 或 VS 中加载 PDB,否则崩溃时只能看到
ntdll!RtlpHeapHandleError,看不到分配源头 - 关闭 ASLR 干扰:临时用
editbin /DYNAMICBASE:NO your_app.exe,避免每次堆地址跳变导致日志难比对
VS 调试器里看不到 page heap 的分配记录?
Visual Studio 默认不解析 page heap 的内部结构,它只认 CRT debug heap(_CrtDumpMemoryLeaks 那套)。page heap 的元数据由 Windows 堆管理器维护,必须用 WinDbg 或 umdh 才能提取。
使用场景:你想知道某块崩溃地址是谁分配的、在哪一行、调用栈是什么——这只能靠 !heap -p -a <address></address>。
- 启动时加
-logo c:\heap.log参数让 WinDbg 自动记录所有 page heap 操作 - 崩溃后第一时间执行
!heap -p -a 0x0000000012345678(把地址换成实际崩溃地址),输出里会带stack trace字段 - 如果 stack trace 是空的,说明该内存不是 page heap 分配的——检查是否用了
VirtualAlloc、静态缓冲区或第三方库自定义分配器
umdh 对比两次快照找不到泄漏,但内存持续上涨?
umdh 只抓 page heap 分配,而很多 C++ 项目混用多种分配方式:new 走 CRT heap,CoTaskMemAlloc 走 COM heap,HeapAlloc(GetProcessHeap()) 走默认堆——这些都不会出现在 umdh 日志里。
参数差异:umdh -f:baseline.txt your_app.exe 生成基线,umdh -f:later.txt your_app.exe 生成后续,再用 umdh baseline.txt later.txt 对比。但若中间进程重启过,两个快照的堆句柄不一致,对比结果无效。
- 必须在同一进程生命周期内采集两次快照:比如在 UI 上点“开始采集”,做操作,再点“结束采集”
- 确认目标进程确实启用了 page heap:运行
gflags /p /q your_app.exe,输出中要有enabled和full - 若用的是 Release 版本,PDB 必须匹配且路径正确,否则 umdh 显示的函数名全是
+0x123,无法定位源码行
page heap + gflags 在 Windows Server 上不生效?
Server 版默认禁用 user-mode page heap,即使 gflags 显示已启用,实际也走普通堆。这是安全策略限制,不是配置遗漏。
性能影响明显:page heap 会让内存占用翻倍(每块前后加 guard page),分配速度下降 3–5 倍,仅适合调试,绝不能留在生产环境。
- 手动开启需修改注册表:
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management\EnablePageHeap设为1,然后重启 - Server 2016+ 推荐改用
Application Verifier替代:它支持更细粒度控制(比如只对某个 DLL 启用 heap checking),且兼容性更好 - 注意 32 位进程在 64 位系统上受 WoW64 限制,page heap 日志可能截断,优先用 64 位调试环境
真正麻烦的是跨 DLL 边界的堆分配——比如 A.dll 用 new 分配,B.dll 用 delete 释放,page heap 能捕获,但调用栈只显示到导出函数入口,原始 new 位置可能被优化掉。这时候得结合编译选项 /Zi 和 /RTC1 补充验证。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!









