重载new和delete检测内存泄漏

2023-05-16

内存泄漏就是new出来的内存没有通过delete合理的释放。
重载new和delete检测内存泄漏原理是:
在重载的new中记录内存分配情况,在重载的delete中删除内存分配记录,从而跟踪所有内存分配信息。

下面代码的是在《Think in C++ V2》第2章中源码的例子基础上修改的。
一点笔记来自《Think in C++ V2》中第2章:
1. 断言是一个肯定语句,用来肯定显示设计意图。断言的意图是验证设计决定,造成它失败的唯一原因应该是程序逻辑有缺陷。
2. 如果想开启或关闭程序中某些位置的断言,使用#define或#undef NDEBUG。
  在编译器选项中定义NDEBUG在release版本中关闭assert,使用命令g++ -D NDEBUG assertTest.cpp。
3. 代码重构,重构是通过改造系统内部的代码从而改进程序的设计,并且不改变程序的行为。

关于下面源码1调试中的一点经验与注意:
1. 在void* operator new(size_t size, const char* file, long line) 中,第一个参数size会被自动填充;
2. 使用重载的new,类的构造函数与析构函数也会被自动调用;
3. 对于new重载的使用,参考之前的总结 《new的理解》和 《C++智能指针auto_ptr》;
4. 在内存信息记录时使用的是数组memMap,使用链表List效率会更高(参考源码2);

5. 详见代码注释;代码还不够完善,有时间继续重构。


//源码1

#include <cassert>
#include <vector>
#include <iostream>
using namespace std;

//INFO日志
#define log_info(...) do {\
printf("<%s %s> %s %s:%d INFO:",__DATE__, __TIME__,__FUNCTION__,__FILE__,__LINE__)&&\
printf(__VA_ARGS__)&&\
printf("\n");\
}while(0)

//ERROR日志
#define log_error(...) do {\
printf("<%s %s> %s %s:%d ERROR:",__DATE__, __TIME__,__FUNCTION__,__FILE__,__LINE__)&&\
printf(__VA_ARGS__)&&\
printf("\n");\
}while(0)

#define INVALID -1

//内存跟踪与日志开关
bool traceFlag = false;
bool memFlag = false;

#define TRACE_ON() traceFlag = true
#define TRACE_OFF() traceFlag = false

#define MEM_ON() memFlag = true
#define MEM_OFF() memFlag = false

//内存跟踪信息表
struct memInfo 
{
  void* ptr;
  const char* file;
  long line;
};

//最大跟踪内存数 
const int MAX_PTRS = 100;
//内存跟踪数组
memInfo memMap[MAX_PTRS] = {0};
//内存跟踪游标
int nPtrs = 0;

//(添加信息)内存跟踪信息表
void addPtr(void* p, const char* f, long l)
{
  memMap[nPtrs].ptr = p;
  memMap[nPtrs].file = f;
  memMap[nPtrs].line = l;
  ++nPtrs;
}

//(查找信息)内存跟踪信息表
int findPtr(void* p) 
{
  for(size_t i = 0; i < nPtrs; ++i)
  {
    if(memMap[i].ptr == p)
      return i;
  }
  return INVALID;
}

//(删除信息)内存跟踪信息表
void delPtr(void* p) 
{
  int pos = findPtr(p);
  assert(pos >= 0);
  
  for(size_t i = pos; i < nPtrs-1; ++i)
  {
    //当删除一个数据,后面的数据依此向前填充
    memMap[i] = memMap[i+1];
  }
  //移动数据后,最后一个位置置为0
  memMap[nPtrs-1].ptr = 0;
  memMap[nPtrs-1].file = 0;
  memMap[nPtrs-1].line = 0;	
  
  --nPtrs;
}

//(输出信息)内存跟踪信息表
void memInfo_output()
{
  for(int i = 0; i < nPtrs; ++i)
  {
	log_info("memMap[%d].ptr= %p", i, memMap[i].ptr);
  }
}

//重载new
void* operator new(size_t size, const char* file, long line) 
{
  //不管是否跟踪内存,都会malloc内存;相当于不使用重载new的情况
  void* p = malloc(size);
  if (p==NULL) 
  {
    log_error("memory allocate failed");
    exit(1);
  }
  if(memFlag) 
  {
    if(nPtrs == MAX_PTRS) 
	{
	  log_error("memory map too small (increase MAX_PTRS)");
	  free(p);//内存跟踪失败,释放
      exit(1);
    }
	addPtr(p, file, line);
  }
  if(traceFlag) 
  {
	log_info("Allocated %u bytes at address %p (Located file: %s, line: %ld)", size, p, file, line);
  }
  return p;
}

// 重载new数组
void* operator new[](size_t size, const char* file, long line) 
{
  return operator new(size, file, line);
}

//宏定义new,在后续调用new时,使用new(__FILE__, __LINE__)
//void* operator new(size_t size, const char* file, long line) 中的第一个参数会被自动填充
#define new new(__FILE__, __LINE__)
//没有测试过带参数的delete重载
//#define delete delete(__FILE__, __LINE__)

//重载delete,可以调试下带参数的delete
//void operator delete(void* p, ***) 
void operator delete(void* p) 
{
  //不管memFlag是否打开,都会释放内存;就是说,不管跟踪内存与否,都会正常释放内存
  free(p); 
  if(memFlag)
  {    
    if(findPtr(p) >= 0) 
    {
      assert(nPtrs > 0);
      delPtr(p);
    }
	//在内存跟踪信息表中找不到,要么为NULL指针,要么为unknow
	else if(!p)
	{
	  log_error("Delete NULL pointer: %p", p);
	  return;
	}
	else
	{
	  log_error("Attempt to delete Unknown pointer: %p", p);
	  p = NULL;
	  return;
	}
  }
  if(traceFlag)
    log_info("Deleted memory at address %p", p);
	
  //p = NULL;free(p) does not change the value of p itself, hence it still points to the same (now invalid) location
  //值为空,free(p) 不会改变指针p的值,p仍然指向相同(非法)的地址。
  p = NULL;
}

// Override array delete
void operator delete[](void* p) 
{
  operator delete(p);
} ///:~

//fooTest类用于测试局部变量自动释放内存
class fooTest 
{
  char* s;
public:
  fooTest(const char*s ) 
  {
    this->s = new char[strlen(s) + 1];
    strcpy(this->s, s);
  }
  ~fooTest() 
  { 
    delete [] s; 
  }
};

//myTest类用于测试在调用重载new的情况下,也会自动调用构造函数与析构函数
class myTest
{
public:
  myTest()
  {
    cout<<"Construct my test..."<<endl;
  }
  ~myTest()
  {
    cout<<"Destruct my test..."<<endl;
  }
};

// Sentinel相当于内存守卫,全局的Sentinel对象将在程序退出前检测内存泄露情况
struct memGuard 
{
  ~memGuard() 
  {
    if(memFlag) 
    {
      if(nPtrs > 0) 
	  {
        for(size_t i = 0; i < nPtrs; ++i)
		  log_error("Leaked memory at: %p (file: %s, line %ld)", memMap[i].ptr, memMap[i].file, memMap[i].line);
      }
      else
	    log_info("No memory leaks!");
	}
  }
};
memGuard mG;

void test()
{
  //如果有undef new,没有undef delete,就不会调用重载的new,而调用重载的delete,内存跟踪会出错。
  //#undef new
  //#undef delete
  
  int* iPtr = new int;
  int* aPtr = new int[3];
  char * str = new char[9];

  memInfo_output();  
  
  delete iPtr;
  //delete iPtr;   重复调用delete,程序crash
  delete [] aPtr;  
  //delete[] str;
  delete str;
  
  //delete NULL 指针
  int* nPtr = NULL;
  delete nPtr;

  // 即使使用重载new,也会调用构造函数与析构函数
  myTest* mtPtr = new myTest;
  delete mtPtr;
  
  //vector 这里不会调到重载的new
  vector<int> v;
  v.push_back(1);
  
  //将调用2次重载new
  fooTest *ftPtr = new fooTest("goodbye");  
  //内存泄露
  //delete ftPtr;
  
  //自动释放内存
  fooTest ft("goodbye");  
}

int main() 
{  
  MEM_ON();
  TRACE_ON();
 
  test();

  //在这里不能够OFF,因为全局变量“析构”在此范围之外。
  //MEM_OFF();
  //TRACE_OFF();
} 

//源码2
//下面的源码来源于网络,仅供参考

#include<iostream>
using namespace std;
//---------------------------------------------------------------
// 内存记录
//---------------------------------------------------------------
class MemInfo {
private:
  void* ptr;
  const char* file;
  unsigned int line;
  MemInfo* link;
  friend class MemStack;
};
//---------------------------------------------------------------
// 内存记录栈 
//---------------------------------------------------------------
class MemStack {
private:
  MemInfo* head;
public:
  MemStack():head(NULL) { }
  ~MemStack() { 
    MemInfo* tmp;
    while(head != NULL) {
      free(head->ptr); // 释放泄漏的内存 
      tmp = head->link;
      free(head);
      head = tmp;
    }
  }
  void Insert(void* ptr, const char* file, unsigned int line) {
    MemInfo* node = (MemInfo*)malloc(sizeof(MemInfo));
    node->ptr = ptr; node->file = file; node->line=line;
    node->link = head; head = node;    
  }
  void Delete(void* ptr) {
    MemInfo* node = head;
    MemInfo* pre = NULL;
    while(node != NULL && node->ptr!=ptr) {
      pre = node;
      node = node->link;
    }
    if(node == NULL)
      cout << "删除一个没有开辟的内存" << endl;
    else {
      if(pre == NULL) // 删除的是head 
        head = node->link;
      else 
        pre->link = node->link;
      free(node);
    }
  }
  void Print() {
    if(head == NULL) {
      cout << "内存都释放掉了" << endl; 
      return;
    }
    cout << "有内存泄露出现" << endl; 
    MemInfo* node = head;    
    while(node != NULL) {
      cout << "文件名: " << node->file << " , " << "行数: " << node->line << " , "
        << "地址: " << node->ptr << endl; 
      node = node->link;
    }
  }
};
//---------------------------------------------------------------
// 全局对象 mem_stack记录开辟的内存 
//---------------------------------------------------------------
MemStack mem_stack;
//---------------------------------------------------------------
// 重载new,new[],delete,delete[] 
//---------------------------------------------------------------
void* operator new(size_t size, const char* file, unsigned int line) {
  void* ptr = malloc(size);
  mem_stack.Insert(ptr, file, line);
  return ptr;
}
void* operator new[](size_t size, const char* file, unsigned int line) {
  return operator new(size, file, line); // 不能用new 
}
void operator delete(void* ptr) {
  free(ptr);
  mem_stack.Delete(ptr);
}
void operator delete[](void* ptr) {
  operator delete(ptr);
}
//---------------------------------------------------------------
// 使用宏将带测试代码中的new和delte替换为重载的new和delete 
//---------------------------------------------------------------
#define new new(__FILE__,__LINE__)
//---------------------------------------------------------------
// 待测试代码 
//---------------------------------------------------------------
void bad_code() {
  int *p = new int;
  char *q = new char[5];
  delete []q;
} 

void good_code() {
  int *p = new int;
  char *q = new char[5];
  delete p;
  delete []q;
} 
//---------------------------------------------------------------
// 测试过程 
//---------------------------------------------------------------
int main() {
  good_code();
  bad_code();
  mem_stack.Print();
  system("PAUSE");
  return 0;
}


本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

重载new和delete检测内存泄漏 的相关文章

  • 1. 两数之和【return new int[]{i, j}、hashtable.containsKey()、get、put】

    1 两数之和 给定一个整数数组 nums 和一个整数目标值 target xff0c 请你在该数组中找出 和为目标值 target 的那 两个 整数 xff0c 并返回它们的数组下标 你可以假设每种输入只会对应一个答案 但是 xff0c 数
  • c#中new一个对象以后,是否需要手动释放?

    c 中new一个对象以后 xff0c 是否需要手动释放 xff1f 2012 04 28 23 43 wshbfzdzb 分类 xff1a C NET 浏览723次 c 43 43 中 class1 a 61 new class1 需要在用
  • C++ std::tr1::shared_ptr使用

    看 effective c 作者一直强调用std tr1 shared ptr 比起auto ptr好多了 shared ptr采用引用计数 多个指针可以指向同一个对象 auto ptr就不能 只能运行一个指针指向一个对象 如果要指针赋值
  • 2009年8月21日

    开通博客了 new Start 1 加了的Active控件Windows Media Player如何能使用快进FastForward 和快退FastForward 这两个功能呢 给控件关联一个control变量 然后调用FastForwa
  • 根据GUID获得设备路径(转载)

    根据GUID获得设备路径 include
  • Email Error - You have exceeded the storage limit on your mailbox

    Description You may receive an error You have exceeded the storage limit on your mailbox Delete some items from your mai
  • c++ 编码转化

    UTF 8到GB2312的转换 char U2G const char utf8 int len MultiByteToWideChar CP UTF8 0 utf8 1 NULL 0 wchar t wstr new wchar t le
  • 攻防世界 web 不能按的按钮 disabled_button

    f12打开开发者工具 点击查看器这一栏 定位到它的图标处 双击进入 里面有代码如下 一般有两种方法 方法一 删除代码 disabled 然后点击网页上的flag图标就可以得到flag了 方法二 将disabled 改为disabled fa
  • 私有构造函数

    通常我们都将构造函数的声明置于public区段 假如我们将其放入private区段中会发生什么样的后果 没错 我也知道这将会使构造函数成为私有的 这意味着什么 我们知道 当我们在程序中声明一个对象时 编译器为调用构造函数 如果有的话 而这个
  • Truncate和Delete的区别

    1 表和索引所bai占空间 当表被truncate 后 这个表和索引所占du用的空间会恢复到初始zhi大小 delete操作不dao会减少表或索引所占用的空间 2 应用范围 truncate 只能对table delete可以是table和
  • 柯理化、mergeOptions、new的实现原理、reduce、flat

    1 什么是反柯理化 怎么实现 反柯里化 是一个泛型化的过程 它使得被反柯里化的函数 可以接收更多参数 Function prototype unCurrying function var that this return function
  • 【C++】教大家在七夕new一个对象

    new是啥 new 是C 的关键字之一 它一般用来在堆上创建对象 但它与一般的创建对象的方法不同 它在创建对象时 会返回这个对象的指针 堆是啥 还有 和栈有什么区别 栈是由编译器自动分配和释放的 一般存放函数的参数值 局部变量的值等 速度较
  • Unicode下CString和char *之间的互相转换

    CString中存储的字符的数据类型为wchar t类型 一 CString转换为char 1 方法一 使用wcstombs include
  • C++设计日志:读写定界符文件

    C 设计日志 读写定界符文件 荣耀 2003 我将撰写的 C 设计实践 系列文章 会讲到一些数据处理系统设计方法 我并不希望文章局限于特定数据库产品 我也不喜欢空对空地讲述太多抽象道理 我必须编写一些模拟数据库操作的代码 用于读写定界符文件
  • MFC窗口销毁过程

    MFC窗口销毁过程 考虑单窗口情况 假设自己通过new创建了一个窗口对象pWnd 然后pWnd gt Create 则销毁窗口的调用次序 1 手工调用pWnd gt DestroyWindow 2 DestroyWin
  • MPI群通信与矩阵乘法的Fox算法实现

    原本以为 MPI天生只能在Linux上运行 但这次却发现了Intel MPI Library 这个好用的东西 基本不需要设置 安上之后 用自己能登录windows的帐号和密码注册就行了 虽然不是局域网上的机器 但也可以让我的双核CPU达到1
  • 指针的删除

    1 在链表中 将某个指针delete 指向该指针的那个指针的next 不会自动赋值为NULL 需要手动赋值 2 删掉 某指针所指向的内存 该指针仍然可以使用 下面是一个带头指针的单向链表 void Stack Pop int value i
  • 20个常见的Java错误以及规避方法

    原文 50 Common Java Errors and How to Avoid Them Part 1 作者 Angela Stringfellow 翻译 雁惊寒 译者注 本文介绍了20个常见的Java编译器错误 每种错误都包含了代码片
  • 如何正确的关闭 MFC 线程

    前言 近日在网上看到很多人问及如何关闭一下线程 但是我看网上给出的并不详细 而且有些方法还是错误的 小弟在此拙作一篇 不谈别的 只谈及如何正确的关闭MFC的线程 至于Win32和C RunTime的线程暂不涉及 一 关于MFC的线程 MFC
  • enum与typedef enum的用法

    前些天发现了一个巨牛的人工智能学习网站 通俗易懂 风趣幽默 忍不住分享一下给大家 点击跳转到网站 https www captainai net db 一 两者的用法 枚举类型定义用关键字enum标识 形式为 enum 标识符 枚举数据表

随机推荐

  • apt-get 安装某个包时出现各种依赖包没有安装、版本不对互相套娃的一种解决方法

    错误信息 Depends 某某包 but it is not going to be installed 或者 Depends 某某包 61 某某版本 but 某某版本 is to be installed 本人是用的树莓派raspbian
  • Python 中更优雅的日志记录方案

    在 Python 中 xff0c 一般情况下我们可能直接用自带的 logging 模块来记录日志 xff0c 包括我之前的时候也是一样 在使用时我们需要配置一些 Handler Formatter 来进行一些处理 xff0c 比如把日志输出
  • 用Python抓包工具查看周边拼车情况

    说起Python爬虫 xff0c 很多人第一个反应可能会是Scrapy或者Pyspider xff0c 但是今天文章里用到是Python下一个叫Mitmproxy的抓包工具 xff0c 对付一些非常规的数据获取还是很有效的 不过凡事都是有利
  • Origin(教育版)无法更改语言是灰色的办法

    用edu后缀的邮箱可以申请正版Origin pro xff0c 但是本人下载下来后是英文版 xff0c 绘图的时候里面很多的专有名词真的一头雾水 xff0c 查看网上的教程说修改注册表 xff0c 试了后发生错误 xff0c 再次打开还是原
  • centos虚拟机切换为命令行模式

    打开命令终端Terminal 输入以下 systemctl set default multi user target 设置成命令模式 或 systemctl set default graphical target 设置成图形模式 最后
  • ubuntu操作不当,桌面图标和菜单栏消失怎么办

    最近配置一些文件没有成功 xff0c 重启后 xff0c 桌面只剩下文件和回收站 xff0c 我们可以通过终端输入指令来解决这个问题 xff0c 亲测有效 先用apt get install指令重装桌面 xff1a 快捷键或者右键打开终端
  • 应用宝-ysdk-米大师 对接道具直购服务器端下单模式 java服务端开发日志

    用于接收回调请求的linux服务器证书配置 xff1a 回调服务器配置分三种 xff0c 前两种是当服务部署在腾讯云上时的配置方式 xff0c 第三种为服务部署在自己服务器上是的配置方式 xff1a hosting应用on CVM xff0
  • 使用python开发json、csv数据格式转换工具

    使用python开发json csv数据格式转换工具 json和xml是业界常用的数据格式 xff0c 而游戏行业经常使用csv配表 xff0c 包括本地化文本和数值 本文介绍csv和json序列化 逆序列化相关的python库 xff0c
  • 【笔记】Ubuntu字体报错

    Warning Cannotconvertstring 34 adobe helvetica medium r normal 120 75 75 p iso8859 1 34 to type FontStruct ENVI 43 IDL o
  • python3-端口扫描(TCP connect扫描,SYN扫描,FIN扫描)

    利用python3进行端口扫描 xff0c TCP的方式有connect扫描 xff0c SYN扫描 xff0c FIN扫描 xff0c NULL扫描 xff0c ACK扫描 xff0c Xmas xff0c windows扫描 本次展示前
  • DirectX(dll)修复软件推荐4.2增强版

    我们经常会遇到在重装完系统后 xff0c 软件或者游戏无法正常运行 xff0c 提示缺少dll文件 xff0c 这时候要么去百度搜索相应dll文件 xff0c 不仅费事还不好找 xff0c 要么就是用DirectX Repair自动扫描安装
  • 未封装的扩展程序

    查看插件 程序展示未封装的扩展程序 如下图 没显示调试工具的原因是用了生产环境的版本或是压缩的vue版本 xff0c 或是没有勾选 xff1a 允许访问文件网址 https span class token punctuation span
  • 2019小结

    2019已经过去 xff0c 回想这过去的一年 xff0c 是近几年来最忙碌的一年 年初 xff0c 参加了一场读书会 xff0c 和公司同事一起读书 一本优秀的书是作者思想的精华 xff0c 通过读书可以认识和了解自己思维以外的世界 虽然
  • 继续前行

    很久没有更新日志了 xff0c 很久没有写博客了 xff1b 我知道我有的时候很忙 xff0c 我知道当前还有更重要的事情 xff0c 我知道事情是做不完的 但不管怎么样 xff0c 我该停下脚步想一想 xff0c 怎么去走好下一步 202
  • 《即兴演讲》读书笔记

    这是一本我一直在寻找的书 xff1b 因为我从事技术工作 xff0c 十多年来每天都和计算机打交道 xff0c 很少有机会站在台上讲话 xff1b 我对演讲是敬畏的 xff0c 想上台表现自己但心里又充满了恐惧 xff1b 偶尔的上台机会只
  • 读“赵4老师”言论

    在查看CSDN论坛时 xff0c 发现 赵4老师 无处不在 xff0c 赵4老师 通常不会直接给出答案 xff0c 而是直接复制言论 赵4老师的语言虽然很 犀利 xff0c 但仔细想想 xff0c 有些还是有道理的 xff0c 至少帮助我从
  • *.map文件

    关于linux程序的map文件 xff0c 网络上资料很少 xff0c 大概看了下map文件 xff0c 虽然理解的不是很透彻 xff0c 但是还是对程序的编译 运行 内存分配有了一点新的认识 1 map文件是程序的全局符号 源文件和代码行
  • python开发环境管理:pip和virtualenv

    python开发环境管理 xff1a pip和virtualenv 不同的python软件需要不同的开发环境 xff0c 互相之间甚至可能有冲突 xff0c 怎么处理 xff1f 使用pip virtualenv和virtualenvwra
  • “异常处理”学习小结

    在我经历过的项目中 xff0c 很少使用异常处理 xff1b 对于问题的调试与追踪 xff0c 基本上都是基于错误码和日志信息 这里的学习总结来自于 lt lt C 43 43 编程思想 第2卷 gt gt 和网络 xff0c 有很多问题的
  • 重载new和delete检测内存泄漏

    内存泄漏就是new出来的内存没有通过delete合理的释放 重载new和delete检测内存泄漏原理是 xff1a 在重载的new中记录内存分配情况 xff0c 在重载的delete中删除内存分配记录 xff0c 从而跟踪所有内存分配信息