内存泄漏一直是 C++ 中比较令人头大的问题, 即便是很有经验的 C++程序员有时候也难免因为疏忽而写出导致内存泄漏的代码。除了基本的申请过的内存未释放外,还存在诸如异常分支导致的内存泄漏等等。本项目将使用 C++ 实现一个内存泄漏检查器。

要检测一个长期运行的程序是否发生内存泄露通常是在应用中设置检查点,分析不同检查点中内存是否长期保持占用而未释放,从本质上来说,这与对一个短期运行的程序进行内存泄露检查是非常类似的。所以本项目中的内存泄漏检查器将实现一个用于短期内存泄露的检查器。

我们不妨编写下面的测试代码用于检测我们的内存泄露,在这个代码中,我们刻意的不去释放某个申请的内存,并刻意的去制造一个异常分支产生的内存泄露,在 /home/shiyanlou/ 目录下新建 LeakDetector 文件夹,并在文件夹下新建 main.cpp 文件:

//
//  main.cpp
//  LeakDetector
//
#include <iostream>// 在这里实现内存泄露检查
#include "LeakDetector.hpp"// 测试异常分支泄露
class Err {
public:Err(int n) {if(n == 0) throw 1000;data = new int[n];}~Err() {delete[] data;}
private:int *data;
};int main() {// 忘记释放指针 b 申请的内存,从而导致内存泄露int *a = new int;int *b = new int;delete a;// 0 作为参数传递给构造函数将发生异常,从而导致异常分支的内存泄露try {Err* e = new Err(0);delete e;} catch (int &ex) {std::cout << "Exception catch: " << ex << std::endl;};return 0;}

内存泄露检查器的设计

要实现内存泄露的检查,我们可以从下面几个点来思考:

  1. 内存泄露产生于 new 操作进行后没有执行 delete
  2. 最先被创建的对象,其析构函数永远是最后执行的

对应这两点,我们可以做下面的操作:

  1. 重载 new 运算符
  2. 创建一个静态对象,用于在原始程序退出时候才调用这个静态对象的析构函数

这样两个步骤的好处在于:无需修改原始代码的情况下,就能进行内存检查。这同时也是我们希望看到的。所以,我们可以在 LeakDetector/LeakDetector.hpp 里首先实现:

//
//  LeakDetector.hpp
//  LeakDetector
//
#ifndef __LEAK_DETECTOR__
#define __LEAK_DETECTOR__void* operator new(size_t _size, char *_file, unsigned int _line);
void* operator new[](size_t _size, char *_file, unsigned int _line);
// 此处宏的作用下一节实现 LeakDetector.cpp 时说明
#ifndef __NEW_OVERLOAD_IMPLEMENTATION__
#define new    new(__FILE__, __LINE__)
#endifclass _leak_detector
{
public:static unsigned int callCount;_leak_detector() noexcept {++callCount;}~_leak_detector() noexcept {if (--callCount == 0)LeakDetector();}
private:static unsigned int LeakDetector() noexcept;
};
static _leak_detector _exit_counter;
#endif

为什么要设计 callCountcallCount 保证了我们的 LeakDetector 只调用了一次。考虑下面的简化代码:

// main.cpp
#include <iostream>
#include "test.h"
int main() {return 0;
}
// test.hpp
#include <iostream>
class Test {
public:static unsigned int count;Test() {++count;std::cout << count << ", ";}
};
static Test test;
// test.cpp
#include "test.hpp"
unsigned int Test::count = 0;

下面我们来逐步实现这个内存泄露检查器。

既然我们已经重载了 new 操作符,那么我们很自然就能想到通过手动管理内存申请和释放,如果我们 delete 时没有将申请的内存全部释放完毕,那么一定发生了内存泄露。接下来一个问题就是,使用什么结构来实现手动管理内存?

不妨使用双向链表来实现内存泄露检查。原因在于,对于内存检查器来说,并不知道实际代码在什么时候会需要申请内存空间,所以使用线性表并不够合理,一个动态的结构(链表)是非常便捷的。而我们在删除内存检查器中的对象时,需要更新整个结构,对于单向链表来说,也是不够便利的。

为此,我们可以继续实现这样一个结构,并创建好 LeakDetector/LeakDetector.cpp 文件:

//
//  LeakDetector.cpp
//  LeakDetector
//
#include <iostream>
#include <cstring>// 在此处定义 _DEBUG_NEW_ 宏,
// 从而在这个实现文件中不再继续重载 new 运算符,
// 从而防止编译冲突
#define __NEW_OVERLOAD_IMPLEMENTATION__
#include "LeakDetector.hpp"typedef struct _MemoryList {struct  _MemoryList *next, *prev;size_t     size;       // 申请内存的大小bool    isArray;    // 是否申请了数组char    *file;      // 存储所在文件unsigned int line;  // 保存所在行
} _MemoryList;
static unsigned long _memory_allocated = 0;     // 保存未释放的内存大小
static _MemoryList _root = {&_root, &_root, // 第一个元素的前向后向指针均指向自己0, false,               // 其申请的内存大小为 0, 且不是数组NULL, 0                 // 文件指针为空, 行号为0
};unsigned int _leak_detector::callCount = 0;

现在我们来使用这个结构来管理内存分配:

//
//  LeakDetector.cpp
//  LeakDetector
//
// 从 _MemoryList 头部开始分配内存
void* AllocateMemory(size_t _size, bool _array, char *_file, unsigned _line) {// 计算新的大小size_t newSize = sizeof(_MemoryList) + _size;// 由于 new 已经被重载,我们只能使用 malloc 来分配内存_MemoryList *newElem = (_MemoryList*)malloc(newSize);newElem->next = _root.next;newElem->prev = &_root;newElem->size = _size;newElem->isArray = _array;newElem->file = NULL;// 如果有文件信息,则保存下来if (_file) {newElem->file = (char *)malloc(strlen(_file)+1);strcpy(newElem->file, _file);}// 保存行号newElem->line = _line;// 更新列表_root.next->prev = newElem;_root.next = newElem;// 记录到未释放内存数中_memory_allocated += _size;// 返回申请的内存,将 newElem 强转为 char* 来严格控制指针每次 +1 只移动一个 bytereturn (char*)newElem + sizeof(_MemoryList);
}

C++ 实现内存泄露检查相关推荐

  1. VC内存泄露检查工具:VisualLeakDetector

    From: http://www.xdowns.com/article/170/Article_3060.html 初识Visual Leak Detector        灵活自由是C/C++语言 ...

  2. Android C++ Native 内存泄露检查工具Raphael使用介绍

    Android C++ Native 内存泄露检查工具使用介绍 实现原理 使用方法 Raphael添加到测试apk 添加项目依赖 同步gradle 启动泄露检测功能 直接使用boardcast功能控制 ...

  3. linux c 内存泄露 检查工具

    Linux下编写C或者C++程序,有很多工具,但是主要编译器仍然是gcc和g++.最近用到STL中的List编程,为了检测写的代码是否会发现内存泄漏,了解了一下相关的知识. 所有使用动态内存分配(dy ...

  4. Linux下几款C++程序中的内存泄露检查工具

    Linux下编写C或者C++程序,有很多工具,但是主要编译器仍然是gcc和g++.最近用到STL中的List编程,为了检测写的代码是否会发现内存泄露,了解了一下相关的知识. 所有使用动态内存分配(dy ...

  5. VC内存泄露检查工具:Visual Leak Detector

    www.diybl.com 时间:2009-04-12 作者:匿名 编辑:sky 初识Visual Leak Detector        灵活自由是C/C++语言的一大特色,而这也为C/C++程序 ...

  6. Visual studio内存泄露检查工具--BoundsChecker

    BoundsChecker是一个Run-Time错误检测工具,它主要定位程序在运行时期发生的各种错误. BoundsChecker能检测的错误包括: 1)指针操作和内存.资源泄露错误,比如:内存泄露: ...

  7. windows客户端性能测试之内存泄露检查工具umdh.exe

    Umdh 是 Debugging Tools for Windows 中的工具,在windbg的安装目录内,UMDH主要通过分析比较进程的Heap Stack trace信息来发现内存泄露的. Umd ...

  8. 【调试手段】linux下valgrind内存泄露检查

    参考文章:http://blog.csdn.net/wzzfeitian/article/details/8567030 文库资料:                                   ...

  9. VS 2008 中内存泄露检查

    #define _CRTDBG_MAP_ALLOC #include <crtdbg.h> #ifdef _DEBUG #define new new(_NORMAL_BLOCK,__FI ...

最新文章

  1. 690啊690,你不是找骂吗?
  2. GPIO做输出还能作外部中断输入吗?
  3. 生成FaceBook所需的散列哈希值
  4. mysql 默认时间字段 1067,mysql替datetime类型字段设置默认值default
  5. 使用Mybatis-plus更新null字段的解决方案
  6. 前端:CSS/09/行内框架,CSS简介,CSS选择器,组合选择器,CSS注释,CSS尺寸属性,CSS字体属性,CSS文本属性
  7. python os.remove拒绝访问_「进阶Python」第八讲:代理模式
  8. SQL Tuning学习杂记
  9. [ZT]javascript window resize 窗口改变事件
  10. latex table 表格 显示每行横线
  11. 全网首发:JDK绘制文字:二、绘制句柄的初始化流程
  12. 微型计算机天逸510s光驱,天逸510s Mini兼macOS BigSur安装教程
  13. Source Insight 使用技巧整理
  14. matlab 最优资产组合,基于MATLAB的最优投资组合问题.pdf
  15. 双线macd指标参数最佳设置_一文讲透双线MACD指标及其实战运用
  16. word 编辑过程中变为只读_word文档保存后,如何恢复之前的资料?
  17. SpringMVC工作原理 侵立删
  18. 解决“javac”提示不是内部或外部命令的问题
  19. depot_tools更新失败
  20. 第六章 网络编程——SOCKET 开发

热门文章

  1. 【虹科终端安全案例】中小企业如何通过移动目标防御(MTD)阻止下一次大型网络攻击
  2. Chained row
  3. Qt Linguist 介绍
  4. Praat脚本-025 | 轻松合并不同目录里的TextGrids
  5. 纸质图书和电子图书选择的三点建议
  6. MPS——超小尺寸 IEEE802.3af PD 解决方案
  7. 使用Windows Server Backup对Exchange进行备份与恢复(一)
  8. 微信小程序导入Vant Weapp组件库
  9. 对未来的一些规划和想法
  10. 华为的主动式电子笔M-Pen2