Masimaro's blog - 操作系统
type: channel
link: https://www.masimaro.top/tag/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/
items: 记一次内存泄露调试

记一次内存泄露调试
type: item
date: 2021-01-10T13:55:00+08:00
description: 首先介绍一下相关背景。最近在测试一个程序时发现，在任务执行完成之后，从任务管理器上来看，内存并没有下降到理论值上。程序在启动完成之后会占用一定的内存，在执行任务的时候，会动态创建一些内存，用于存储任务的执行状态，比如扫描了哪些页面，在扫描过程中一些收发包的记录等等信息。这些中间信息在任务结束之后会被清理掉。任务结束之后，程序只会保存执行过的任务列表，从理论上讲，任务结束之后，程序此时所占内存应该与程序刚启动时占用内存接近，但是实际观察的结果就是任务结束之后，与刚启动之时内存占用差距在100M以上，这很明显不正常，当时我的第一反应是有内存泄露内存泄露排查既然有内存泄露，那么下一步就是开始排查，由于程序是采用MFC编写的，那么自然就得找MFC的内存泄露排查手段。根据网上找到的资料，MFC在DEBUG模式中可以很方便的集成内存泄露检查机制的。首先在 stdafx.h 的头文件中加入#define _CRTDBG_MAP_ALLO #include <crtdbg.h>再在程序退出的地方加入代码_CrtDumpMemoryLeaks();如果发生内存泄露的话，在调试运行结束之后，观察VS的输出情况可以看到如下内容Detected memory leaks! Dumping objects -> .\MatriXayTest.cpp(38) : {1301} normal block at 0x0000000005584D30, 40 bytes long. Data: < > CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD Object dump complete.在输出中会提示有内存泄露，下面则是泄露的具体内容，MatriXayTest.cpp 是发生泄露的代码文件，括号中的38代表代码所在行数，大括号中1301代表这是程序的第1301次分配内存，或者说第1301次执行malloc操作，再往后就是内存泄露的地址，以及泄露的大小，这个地址是进程启动之后随机分配的地址，参考意义不大。下面一行表示，当前内存中的具体值，从值上来看应该是分配了内存但是没有初始化。根据这个线索，我们来排查，找到第38行所在位置int *p = new int[10]; _CrtDumpMemoryLeaks(); return nRetCode;内存泄露正是出现在new了10个int类型的数据，但是后面没有进行delete操作，正是这个操作导致了内存泄露。到此为止，检测工具也找到了，下面就是加上这段代码，运行发生泄露的程序，查看结果再漫长的等待任务执行完成并自动停止之后，我发现居然没有发现内存泄露！！！我又重复运行任务多次，发现结果是一样的，这个时候我开始怀疑是不是这个库不准，于是我在数据节点的类中添加构造函数，统计任务执行过程中创建了多少个节点，再在析构中统计析构了多少个节点，最终发现这两个数据完全相同。也就是说真的没有发生内存泄露。在这里我也疑惑了，难道是任务管理器有问题？带着这个疑问，我自己写了一段代码，在程序中不定时打印内存占用情况，结果发现虽然与任务管理器有差异，但是结果是类似的，释放之后内存并没有大幅度的下降。我带着疑问查找资料的过程的漫长过程中，发现任务管理器的显示内存占用居然降下去了，我统计了一下时间，应该是在任务结束之后的30分钟到40分钟之间。带着这个疑问，我又重新发起任务，在任务结束，并等待一定时间之后，内存占用果然降下去了。这里我得出结论 程序中执行delete操作之后，系统并不会真的立即回收操作，而是保留这个内存一定时间，以便后续该进程在需要分配时直接使用结论验证科学一般来说需要大胆假设，小心求证，既然上面根据现象做了一些猜想，下面就需要对这个猜想进行验证。首先来验证操作系统在程序调用delete之后不会真的执行delete操作。我使用下面的代码进行验证//定义一个占1M内存的结构 struct data{ char c[1024 * 1024]; } data* pa[1024] = {0}; for (int i = 0; i < 1024; i++) { pa[i] = new data; //这里执行一下数据清零操作，以便操作系统真正为程序分配内存 //有时候调用new或者malloc操作，操作系统只是保留对应地址，但是并未真正分配物理内存 //操作会等到进程真正开始操作这块内存或者进程需要分配的内存总量达到一个标准时才真正进行分配 memset(pa[i], 0x00, sizeof(data)); } printf("内存分配完毕，按任意键开始释放内存...\n"); getchar(); for (int i = 0; i < 1024; i++) { delete pa[…