# Windows C++ 内存泄漏调试技术
目录
# 内存泄漏特征
先用Process Explorer工具检查目标进程,下载地址: (进程资源管理器 - Sysinternals | Microsoft Learn (opens new window)
观察Private Bytes增长情况
可见内存呈增长趋势说明有内存泄漏
# 内存泄漏静态代码排查方向
排查项目里面申请内存关键字new、malloc、HeapAlloc、VirtualAlloc、GlobalAlloc等,检查代码上下文,看申请到的内存是否有配对的释放函数。
排查项目API调用相关代码,关键字如:open、create等字样的API,检查它们是否需要调用对应的close函数。
排查项目里面的HANDLE、HKEY、FILE等类型,检查它们在生命周期结束后是否正确close。
排查项目第三方库,项目里面使用第三方库,如JSON、curl、SQLite,检查所用到的API,是否遵循文档指南调用释放内存函数。
# 内存泄漏问题动态排查
安装Windbg指定版本,其他版本可能无法看到堆内存占用排序
需要确定目标进程或者dump是32位还是64位,选取指定平台的windbg,否则可能无法查看调用栈情况。
进入Windbg目录下,找到gflags.exe运行,给目标进程增加userstack trace。
打开:gflags.exe /i <进程名称> +ust 关闭:gflags.exe /i <进程名称> -ust
设置代码和PDB目录,启动调试进程
启动进程
运行一段时间,暂停检查堆分配情况
运行堆检查命令
!heap -s
再go恢复运行一段时间,然后再调用!heap -s查看当前堆尺寸增长
如检查堆地址0e0b0000情况:!heap -stat -h 0e0b0000
检查尺寸为67e80大小的内存所有分配情况 !heap -flt s 67e80
检查这些用户指针是在哪个函数分配的
!heap -p -a 4eb73ce0
如果有PDB可以查看函数对应代码位置,使用u命令,检查指定函数地址代码位置
u xxxDll!xxxxxx::GetRunStatus+0x00000b6e
# 在代码启用运行时库内存泄漏检测
如动态或静态排查无头绪,可以在代码启动运行时库代码检查,配合vs debug调试
[https://docs.microsoft.com/zh-cn/visualstudio/debugger/finding-memory-leaks-using-the-crt-library?view=vs-2022](使用 CRT 库查找内存泄漏 - Visual Studio (Windows) | Microsoft Learn (opens new window))
检测内存泄漏的主要工具是 C/C++ 调试器和 C 运行时库 (CRT) 调试堆函数。 若要启用调试堆的所有函数,在 C++ 程序中,按以下顺序包含以下语句:
#define _CRTDBG_MAP_ALLOC
#include <stdlib.h>
#include <crtdbg.h>
使用上面的语句启用调试堆函数后,在应用出口点之前放置 _CrtDumpMemoryLeaks 以在应用退出时显示内存泄漏报告。
_CrtDumpMemoryLeaks();
完整代码如下:
#define _CRTDBG_MAP_ALLOC
#include <stdlib.h>
#include <crtdbg.h>
#include <iostream>
int main()
{
char* p = (char*)malloc(51);
_CrtDumpMemoryLeaks();
return 0;
}
结束后调试窗口输出如下,显示有内存泄漏:
# 句柄泄漏排查
句柄使用完未关闭也会导致内存泄漏
句柄: 在windows程序中,有各种各样的资源(窗口、图标、光标等),系统在创建这些资源时会为他们分配内存,并返回标示这些资源的标示号,即句柄 [1] 。 句柄指的是一个核心对象在某一个进程中的唯一索引,而不是指针。 由于地址空间的限制,句柄所标识的内容对进程是不可见的,只能由操作系统通过进程句柄列表来进行维护。句柄列表:每个进程都要创建一个句柄列表,这些句柄指向各种系统资源,比如信号量,线程,和文件等,进程中的所有线程都可以访问这些资源 [2] 。
用Process Explorer工具检查目标进程:
运行一段时间后再观察,有明显增长
此时说明有句柄泄漏。打开句柄视图
检查对应句柄在代码打开的位置,检查他们在使用完毕后是否被正确关闭。