Windows平台下的内存泄漏检测

news2024/11/15 8:25:07

Windows平台下的内存泄漏检测

  • 一、使用_CrtDumpMemoryLeaks定位内存泄露
    • 添加对应的头文件
    • 转储内存泄漏信息
    • 程序任意点退出
    • 指定调试信息输出
  • 二、定位具体内存泄露位置
    • 内存快照
    • 转储内存快照
    • 比较内存快照
    • 完整例子
  • 三、使用WinDbg定位
    • 获取堆信息
    • 查看指定堆的使用情况
    • 获取地址信息
    • 获取地址调用堆栈

请添加图片描述
更多资讯、知识,微信公众号搜索:“上官宏竹”。


Windows平台下面Visual Studio 调试器和 C 运行时 (CRT) 库为我们提供了检测和识别内存泄漏的有效方法,原理大致如下:内存分配要通过CRT在运行时实现,只要在分配内存和释放内存时分别做好记录,程序结束时对比分配内存和释放内存的记录就可以确定是不是有内存泄漏。

一、使用_CrtDumpMemoryLeaks定位内存泄露

添加对应的头文件

在程序中包括以下语句: (#include 语句必须采用上文所示顺序。 如果更改了顺序,所使用的函数可能无法正常工作。)

#define _CRTDBG_MAP_ALLOC
#include <stdlib.h>
#include <crtdbg.h>

通过包括 crtdbg.h,将 mallocfree 函数映射到它们的调试版本,即 _malloc_dbg _free_dbg,这两个函数将跟踪内存分配和释放。 此映射只在调试版本(在其中定义了_DEBUG)中发生。 发布版本使用普通的 malloc 和 free 函数。
_CRTDBG_MAP_ALLOC在应用程序的调试版本中定义标志时,堆函数的基本版本将直接映射到其调试版本。 该标志在 Crtdbg.h 中用于执行映射。 此标志仅在应用程序中定义标志时才 _DEBUG 可用。并非绝对需要该语句;但如果没有该语句,内存泄漏转储包含的有用信息将较少。

转储内存泄漏信息

在程序退出前调用_CrtDumpMemoryLeaks()函数来转储内存泄漏信息。

#define _CRTDBG_MAP_ALLOC
#include <stdlib.h>
#include <crtdbg.h>
 
#include <iostream>
using namespace std;
 
void GetMemory(char *p, int num)
{
    p = (char*)malloc(sizeof(char) * num);
}
 
int main(int argc,char** argv)
{
    char *str = NULL;
    GetMemory(str, 100);
    cout<<"Memory leak test!"<<endl;
    _CrtDumpMemoryLeaks();
    return 0;
}

控制台上会输出检测到内存泄露的信息,如下:

Detected memory leaks!
Dumping objects ->
E:\code\ConsoleApplication1\ConsoleApplication1.cpp(10) : {151} normal block at 0x01604E58, 100 bytes long.
 Data: <                > CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD 
Object dump complete.

输出的信息包括下面几条:

  • 内存分配编号
    {151}
  • 块类型(普通、客户端或 CRT)
    normal block
    普通块”是由程序分配的普通内存。
    “客户端块”是由 MFC 程序用于需要析构函数的对象的特殊类型内存块。 MFC new 操作根据正在创建的对象的需要创建普通块或客户端块。
    “CRT 块”是由 CRT 库为自己使用而分配的内存块。 CRT 库处理这些块的释放,因此您不大可能在内存泄漏报告中看到这些块,除非出现严重错误(例如 CRT 库损坏)。
    从不会在内存泄漏信息中看到下面两种块类型:
    “可用块”是已释放的内存块。
    “忽略块”是您已特别标记的块,因而不出现在内存泄漏报告中。
  • 十六进制形式的内存位置。
    0x01604E58
  • 以字节为单位的块大小。
    100 bytes long
  • 前 16 字节的内容(亦为十六进制)。
    Data: < > CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD

如果没有使用 #define _CRTDBG_MAP_ALLOC 语句,则不会显示在其中分配泄漏的内存的文件,如上述的E:\code\ConsoleApplication1\ConsoleApplication1.cpp(10)信息。

程序任意点退出

如果程序总是在同一位置退出,调用 _CrtDumpMemoryLeaks 将非常容易。 如果程序从多个位置退出,则无需在每个可能退出的位置放置对 _CrtDumpMemoryLeaks 的调用,而可以在程序开始处包含以下调用:

_CrtSetDbgFlag ( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );

该语句在程序退出时自动调用 _CrtDumpMemoryLeaks。 必须同时设置 _CRTDBG_ALLOC_MEM_DF_CRTDBG_LEAK_CHECK_DF 两个位域。

指定调试信息输出

默认情况下,_CrtDumpMemoryLeaks 将内存泄漏信息 dump 到 Output 窗口的 Debug 页, 如果你想将这个输出定向到别的地方,可以使用 _CrtSetReportMode 进行重置。
_CrtSetReportMode指定的目标生成的特定报表类型,需要在debug环境下才会生效。

int _CrtSetReportMode(
   int reportType,
   int reportMode
);

参数

  • reportType 报告类型
报告类型描述
_CRT_WARN不需要立即关注的警告、消息和信息。
_CRT_ERROR错误、不可恢复的问题和需要立即关注的问题。
_CRT_ASSERT断言失败 (断言表达式的计算结果为FALSE)。
  • reportMode 新报告模式
报告模式_CrtDbgReport 行为
_CRTDBG_MODE_DEBUG将消息写入调试器的输出窗口。
_CRTDBG_MODE_FILE将消息写入用户提供的文件句柄。 应调用 _CrtSetReportFile 来定义要用作目标的特定文件或流。
_CRTDBG_MODE_WNDW创建一个消息框显示消息以及中止,重试,并忽略按钮。

返回值
成功完成后, _CrtSetReportMode返回上一个报告模式中指定的报告类型reportType。 如果为传入的值无效reportType或为指定无效模式reportMode _CrtSetReportMode调用无效参数处理程序作为中所述参数验证。 如果允许执行继续,此函数可设置errno到EINVAL并返回-1。

二、定位具体内存泄露位置

通过上面的方法,我们几乎可以定位到是哪个地方调用内存分配函数malloc和new等,如上例中的GetMemory函数中,即第10行!但是不能定位到,在哪个地方调用GetMemory()导致的内存泄漏,而且在大型项目中可能有很多处调用GetMemory。如何要定位到在哪个地方调用GetMemory导致的内存泄漏?
定位内存泄漏的另一种技术涉及在关键点对应用程序的内存状态拍快照。 CRT 库提供一种结构类型 _CrtMemState,您可用它存储内存状态的快照。

内存快照

_CrtMemCheckpoint对给定点的内存状态拍快照,它会把当前内存的快照填充到 _CrtMemState结构中。

void _CrtMemCheckpoint(
  _CrtMemState *state
);

转储内存快照

通过向 _CrtMemDumpStatistics 函数传递 _CrtMemState 结构,可以在任意点转储该结构的内容。

比较内存快照

若要确定代码中某一部分是否发生了内存泄漏,可以在该部分之前和之后对内存状态拍快照,然后使用 _CrtMemDifference 比较这两个状态:

_CrtMemState s1, s2, s3;
_CrtMemCheckpoint( &s1 );
// memory allocations take place here
_CrtMemCheckpoint( &s2 );
 
if ( _CrtMemDifference( &s3, &s1, &s2) )
   _CrtMemDumpStatistics( &s3 );

顾名思义,_CrtMemDifference 比较两个内存状态(s1 和 s2),生成这两个状态之间差异的结果(s3)。 在程序的开始和结尾放置 _CrtMemCheckpoint 调用,并使用_CrtMemDifference 比较结果,是检查内存泄漏的另一种方法。
如果检测到泄漏,则可以使用 _CrtMemCheckpoint 调用通过二进制搜索技术来划分程序和定位泄漏。

完整例子

#define _CRTDBG_MAP_ALLOC
#include <stdlib.h>
#include <crtdbg.h>
 
#include <iostream>
using namespace std;
 
_CrtMemState s1, s2, s3;
 
void GetMemory(char *p, int num)
{
    p = (char*)malloc(sizeof(char) * num);
}
 
int main(int argc,char** argv)
{
    _CrtMemCheckpoint( &s1 );
    char *str = NULL;
    GetMemory(str, 100);
    _CrtMemCheckpoint( &s2 );
    if ( _CrtMemDifference( &s3, &s1, &s2) )
        _CrtMemDumpStatistics( &s3 );
    cout<<"Memory leak test!"<<endl;
    _CrtDumpMemoryLeaks();
    return 0;
}

程序输出如下,这说明在s1和s2之间存在内存泄漏!

0 bytes in 0 Free Blocks.
100 bytes in 1 Normal Blocks.
0 bytes in 0 CRT Blocks.
0 bytes in 0 Ignore Blocks.
0 bytes in 0 Client Blocks.
Largest number used: 0 bytes.
Total allocations: 100 bytes.
Detected memory leaks!
Dumping objects ->
E:\code\ConsoleApplication1\ConsoleApplication1.cpp(12) : {151} normal block at 0x008A4E58, 100 bytes long.
 Data: <                > CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD 
Object dump complete.

三、使用WinDbg定位

使用WinDbg定位内存泄露,主要是使用到它的扩展命令!heap
内存泄露代码如下:

class Bad
{
public:
	void AllocMemory()
	{
		for (auto i = 0; i < 100; ++i)
		{
			char* p = new char[5000];
		}
	}
};
 
int main()
{
	Bad b;
	b.AllocMemory();
	return 0;
}

很简单吧,可以一眼看出哪儿有内存泄露,现在我们就来看看WinDbg是怎么去发现的。

获取堆信息

在程序执行前,我们先看一下堆的情况。在WinDbg命令行中输入!heap -s显示所有堆的摘要信息:

0:000> !heap -s
       Failed to read heap keySEGMENT HEAP ERROR: failed to initialize the extention
       NtGlobalFlag enables following debugging aids for new heaps:
       stack back traces
       LFH Key                   : 0xe48d63c61a6de263
       Termination on corruption : ENABLED
          Heap     Flags   Reserv  Commit  Virt   Free  List   UCR  Virt  Lock  Fast 
                            (k)     (k)    (k)     (k) length      blocks cont. heap 
-------------------------------------------------------------------------------------
000001e134530000 08000002    1220     60   1020      2     2     1    0      0   LFH
000001e134500000 08008000      64      4     64      2     1     1    0      0      
-------------------------------------------------------------------------------------

然后按F5执行程序,命中断点后停下来。再来看一下堆信息:

0:000> !heap -s
        Failed to read heap keySEGMENT HEAP ERROR: failed to initialize the extention
        NtGlobalFlag enables following debugging aids for new heaps:
        stack back traces
        LFH Key                   : 0xe48d63c61a6de263
        Termination on corruption : ENABLED
          Heap     Flags   Reserv  Commit  Virt   Free  List   UCR  Virt  Lock  Fast 
                            (k)     (k)    (k)     (k) length      blocks cont. heap 
-------------------------------------------------------------------------------------
000001e134530000 08000002    1220    652   1020     24     8     1    0      0   LFH
000001e134500000 08008000      64      4     64      2     1     1    0      0      
-------------------------------------------------------------------------------------

这里我们看到,地址为0x000001e134530000的堆有明显增长,之前Commit是60K,现在是652K。

查看指定堆的使用情况

然后,我们使用命令!heap -stat -h 000001e134530000进行查看,其中参数-stat表示显示指定堆的使用情况统计信息,-h指定要查看的堆地址,这里是0x000001e134530000

0:000> !heap -stat -h 000001e134530000
     heap @ 000001e134530000
 group-by: TOTSIZE max-display: 20
    size  #blocks     total     ( %) (percent of total busy bytes)
    13bc 64 - 7b570  (90.02)
    1cf0 1 - 1cf0  (1.32)
    30 8d - 1a70  (1.21)
    1234 1 - 1234  (0.83)
    1034 1 - 1034  (0.74)
    df4 1 - df4  (0.64)
    ...

我们看到,大小为0x13bc的块有0x64个,总大小0x7B570, 占整个正在使用块的90.02%。我们怀疑这些块就是泄露的块。

获取地址信息

接下来我们获取这些块的地址。使用命令!heap -flt s 13bc。其中-flt将显示范围限定为指定大小或大小范围的堆,参数s 13bc就是指定大小为0x13bc的块。

0:000> !heap -flt s 13bc
    _HEAP @ 1e134530000
              HEAP_ENTRY Size Prev Flags            UserPtr UserSize - state
        000001e134546ce0 013f 0000  [00]   000001e134546d10    013bc - (busy)
          unknown!noop
        000001e1345480d0 013f 013f  [00]   000001e134548100    013bc - (busy)
        000001e1345494c0 013f 013f  [00]   000001e1345494f0    013bc - (busy)
        000001e13454a8b0 013f 013f  [00]   000001e13454a8e0    013bc - (busy)
        000001e13454bca0 013f 013f  [00]   000001e13454bcd0    013bc - (busy)
		...

这里我只截取了部分数据,其实这儿比较长。这里我们会看到很多状态为busy的堆块,这些堆块应该就是没有释放的内存空间。

获取地址调用堆栈

我们使用!heap -p -a 000001e134546ce0,来输出一下它的调用堆栈:

0:000> !heap -p -a 000001e134546ce0 
    address 000001e134546ce0 found in
    _HEAP @ 1e134530000
              HEAP_ENTRY Size Prev Flags            UserPtr UserSize - state
        000001e134546ce0 013f 0000  [00]   000001e134546d10    013bc - (busy)
          unknown!noop
        7ff9c9d3d6c3 ntdll!RtlpAllocateHeapInternal+0x00000000000947d3
        7ff9730dd480 ucrtbased!heap_alloc_dbg_internal+0x0000000000000210
        7ff9730dd20d ucrtbased!heap_alloc_dbg+0x000000000000004d
        7ff9730e037f ucrtbased!_malloc_dbg+0x000000000000002f
        7ff9730e0dee ucrtbased!malloc+0x000000000000001e
        7ff60b1c1f73 Test!operator new+0x0000000000000013
        7ff60b1c19f3 Test!operator new[]+0x0000000000000013
        7ff60b1c1e10 Test!Bad::AllocMemory+0x0000000000000040
        7ff60b1c4746 Test!main+0x0000000000000046
        7ff60b1c1eb9 Test!invoke_main+0x0000000000000039
        7ff60b1c1d5e Test!__scrt_common_main_seh+0x000000000000012e
        7ff60b1c1c1e Test!__scrt_common_main+0x000000000000000e
        7ff60b1c1f4e Test!mainCRTStartup+0x000000000000000e
        7ff9c83354e0 KERNEL32!BaseThreadInitThunk+0x0000000000000010
        7ff9c9c8485b ntdll!RtlUserThreadStart+0x000000000000002b

在这里,我们看到了这个堆的调用堆栈,Test!Bad::AllocMemory,确实是我们分配没有释放的内存空间。这就是这个堆块分配的堆栈信息,通过这个信息,我们就可以定位到这块内存是哪里分配的,然后再到相应的函数里面去分析。
真正在项目中,情况远没有这种简单,有时候,打印出来的堆信息就有很长一串,这就需要在这些信息里面去找有用的信息的。

参考:Windows下检测内存泄露的方法


请添加图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/133408.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Docker】初级篇

【Docker】初级篇&#xff08;一&#xff09;Docker简介【1】docker是什么【2】容器与虚拟机比较【3】能干嘛【4】去哪下&#xff08;二&#xff09;Docker安装【1】前提说明【2】Docker的基本组成【3】安装步骤&#xff08;1&#xff09;确定是CentOS7及以上版本&#xff08;2…

抽烟打电话行为识别系统 yolo

抽烟打电话行为识别系统通过yolo深度学习框架模型&#xff0c;对现场画面区域进行7*24小时实时监测&#xff0c;发现抽烟打电话等违规行为立即抓拍存档预警。YOLOv5是一种单阶段目标检测算法&#xff0c;该算法在YOLOv4的基础上添加了一些新的改进思路&#xff0c;使其速度与精…

【 shell 编程 】第4篇 数组和函数

数组和函数 文章目录数组和函数一、数组1.普通数组2.关联数组3.数组和循环二、函数1.定义函数2.调用函数一、数组 变量&#xff1a;用一个固定的字符串&#xff0c;代替一个不固定字符串。 数组&#xff1a;用一个固定的字符串&#xff0c;代替多个不固定字符串。 1.普通数组…

Python代码实现学生管理系统

Python代码实现学生管理系统 需求说明 实现一个命令行版本的学生管理系统 功能: 新增学生 显示学生 查找学生 删除学生 存档到文件 创建入口函数 使用一个全局列表 students 表示所有学生信息. 使用 menu 函数和用户交互. 这是一个自定义函数. 使用 insert , show ,…

MacOS Ventura安装失败的原因及解决方法分享

2022年10月&#xff0c;苹果公司向Mac电脑用户推送了MacOS Ventura正式版更新&#xff0c;此次更新为MacOS带来了台前调度、连续互通相机、iMessage 撤回、编辑等功能。吸引众多Mac电脑用户不由纷纷下载安装&#xff0c;但各用户在安装的过程中经常遇到更新MacOS Ventura时突然…

物联网与射频识别技术,课程实验(五)

实验5—— 基于随机二进制树的防冲突算法的实现与性能分析 实验说明&#xff1a; 利用Python或Matlab模拟基于随机二进制树的防冲突算法&#xff1b; 分析标签数量k对遍历所有标签所需时间的影响&#xff1b; 分析标签ID的长度、分布对算法性能的影响&#xff1b; 利用Python或…

MQTT+STM32+ESP8266-01s硬件传递的JSON数据到前端和后端出现中文乱码问题

最近在做一个关于MQTT相关毕设项目,数据传输过程中出现了中文乱码问题,大致就是硬件发送的JSON主题数据中包含中文(如下图1所示),软件后端和软件前端接受该主题数据后出现了中文乱码,出现乱码一般都是硬件传递到后端和前端的编码不一致导致的,所以前端和后端接受该JSON数据的时…

2023.1.1 学习周报

文章目录摘要文献阅读1.题目2.摘要3.问题和方案4.介绍5.方法5.1 Symbolic Description5.2 The Short-Term Memory Priority Model5.3 The STAMP Model5.4 The Short-Term Memory Only Model6.实验6.1 评价指标6.2 实验结果7.结论深度学习加性模型点积模型缩放点积模型双线性模型…

数值优化之凸函数

本文ppt来自深蓝学院《机器人中的数值优化》 目录 1 凸函数的性质 ​2 凸函数的性质 1 凸函数的性质 凸函数最重要的性质就是Jensens inequality,也就是琴生不等式。 若能取到等号则是凸函数&#xff0c;若不能取到等号则是强凸函数&#xff0c;若不等号相反&#xff0c;则…

spring session

文章目录Spring Session 架构及应用场景为什么要spring-sessionSR340规范与spring-session的透明继承Spring Session探索特点核心 APIservlet session 与 spring-session 关系webflux 与 spring session 的关系基于 Servlet 的 Spring Session 实现思考题背景1、注册到 Filter …

Java 并发编程知识总结【一】

JUC 是什么&#xff1f; java.util.concurrent 在并发编程中使用的工具类 concurrent:并发 1. 线程基础知识复习 1.1 进程(process) 进程是程序的一次执行过程&#xff0c;或是正在运行的一个程序。是一个动态的过程&#xff1a;有它自身的产生、存在和消亡的过程(生命周期…

【数据集7】全球人类住区层GHSL数据详解

全球人类住区层Global Human Settlement Layer 官网地址-GHSL - Global Human Settlement Layer 1 全球人类住区层GHS-SMOD Global human settlement layer-settlement model grid (GHS-SMOD)&#xff1a;描述 epoch时段: 1975-2030年 5年一个周期resolution空间分辨率: …

Codeforces Round #833 (Div. 2)E. Yet Another Array Counting Problem(笛卡尔树+树形DP)

题目链接&#xff1a;Problem - E - Codeforces 样例输入&#xff1a; 4 3 3 1 3 2 4 2 2 2 2 2 6 9 6 9 6 9 6 9 9 100 10 40 20 20 100 60 80 60 60样例输出&#xff1a; 8 5 11880 351025663题意&#xff1a;给定一个长度为n的数组a[],对于每一个区间[l,r]&#xff0c;这个…

[Python从零到壹] 六十一.图像识别及经典案例篇之基于纹理背景和聚类算法的图像分割

祝大家新年快乐&#xff0c;阖家幸福&#xff0c;健康快乐&#xff01; 欢迎大家来到“Python从零到壹”&#xff0c;在这里我将分享约200篇Python系列文章&#xff0c;带大家一起去学习和玩耍&#xff0c;看看Python这个有趣的世界。所有文章都将结合案例、代码和作者的经验讲…

尚医通-查询删除科室接口-添加查询删除排班接口实现(二十)

目录&#xff1a; &#xff08;1&#xff09;数据接口-查询和删除科室接口-功能实现 &#xff08;2&#xff09;数据接口-排版接口-功能实现 &#xff08;1&#xff09;数据接口-查询和删除科室接口-功能实现 查看医院系统中查询科室的对应的方法 查询条件需要用的类&#…

【数据结构】链式存储:链表

目录 &#x1f947;一&#xff1a;初识链表 &#x1f392;二、链表的实现&#xff08;单向不带头非循环&#xff09; &#x1f4d8;1.创建节点类 &#x1f4d2;2.创建链表 &#x1f4d7;3.打印链表 &#x1f4d5;4.查找是否包含关键字key是否在单链表当中 &#x1f4d9;…

Webpack核心概念

1. 核⼼概念 Entry Entry ⽤来指定 webpack 的打包⼊⼝。 依赖图的⼊⼝是 entry&#xff0c;对于⾮代码⽐如图⽚、字体依赖也会不断加⼊到依赖图中。 Entry 的⽤法&#xff1a; 1. 单⼊⼝&#xff1a;entry 是⼀个字符串&#xff1b; module.exports {entry: ./path/to/my…

若依框架-补充篇:Vuex全局状态管理Axios二次封装

在上一篇《若依框架&#xff1a;前端登录组件与图像验证码|用户登录逻辑》中的篇末&#xff0c;对Vuex全局状态管理、Axios二次封装部分介绍的较为粗略&#xff0c;因此就有了这个补充篇。 目录 Vuex全局状态管理 Vuex是什么&#xff1f; 如何理解“状态管理模式”&#xf…

【Java语法】之String类练习1

目录 1.字符串中的第一个唯一字符 2. 最后一个单词的长度 58. 最后一个单词的长度 3.验证回文串 4.字符串相加 5.小结&#xff1a; 1.字符串中的第一个唯一字符387. 字符串中的第一个唯一字符https://leetcode.cn/problems/first-unique-character-in-a-string/ 给定一个字符…

【免费开放源码】审批类小程序项目实战(活动申请详解)

第一节&#xff1a;什么构成了微信小程序、创建一个自己的小程序 第二节&#xff1a;微信开发者工具使用教程 第三节&#xff1a;深入了解并掌握小程序核心组件 第四节&#xff1a;初始化云函数和数据库 第五节&#xff1a;云数据库的增删改查 第六节&#xff1a;项目大纲以及制…