重载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检测内存泄漏 的相关文章

  • Mac OS X のキーボードショートカット

    概要 記事 Mac OS X 一般的 紹介 使 上 組 合 同時 押 Mac OS X 機能 呼 出 操作 利用 修飾 文字 同時 押 現在選択 範囲 貼 付 command 刻印 C 同時 押 操作 command C 呼 修飾 多数 組
  • 通过修改注册表设置代理

    reg add HKCU Software Microsoft Windows CurrentVersion Internet Settings v ProxyEnable t REG DWORD d 1 f 作用 使用自动配置脚本 reg
  • 多表可更新视图的实现

    多表关联后的数据能保证主键唯一的视图是可直接做更新 不需要用触发器实现 多表视图的定义 当视图的数据源只有一张数据表 则该视图为单表视图 当视图的数据源是多张数据表 则该视图为多表视图 可更新视图的定义 在绝大多数人的概念中 视图是只读的
  • 释放内存总结

    1 用new malloc显式分配在堆上的内存才需要用delete free释放 而在栈上分配的如int array 80 在子程序结束时自动释放 分配与释放都和数据类型无关 2 delete一个指针的时候 一定要检查是否为null 否则是
  • 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
  • C++ malloc/free/new/delete详解(内存管理)

    这里写目录标题 malloc free 典型用法 内存分配 实现过程 brk和mmap 申请小于128k的内存 申请大于128k的内存 释放内存 brk和mmap的区别 new delete 典型用法 内存分配 实现过程 new delet
  • c++之A a和A *a=new A()

    new是在堆上分配内存 它需要用delete释放 否则会造成内存泄漏 A a 在程序执行完毕后 会自动释放内存 int main A a 定义了一个对象 A p new A 在堆上定义了一个对象 它的指针保存在p里 堆上定义的对象没有名字
  • do...while(0)的妙用

    在C 中 有三种类型的循环语句 for while 和do while 但是在一般应用中作循环时 我们可能用for和while要多一些 do while相对不受重视 但是 最近在读我们项目的代码时 却发现了do while的一些十分聪明的用
  • new与默认构造函数

    class A public A int i int 0 private int a b class B public 无需构造函数 因为数据成员的全部为public int a b c int main A a 3 B b 1 2 3 A
  • C++深拷贝和浅拷贝

    C 深拷贝 浅拷贝 对于普通类型的对象来说 它们之间的复制是很简单的 例如 int a 88 int b a 而类对象与普通对象不同 类对象内部结构一般较为复杂 存在各种成员变量 下面看一个类对象拷贝的简单例子 include
  • C++内存管理

    http blog csdn net zhanghefu article details 5003383 内存管理是C 最令人切齿痛恨的问题 也是C 最有争议的问题 C 高手从中获得了更好的性能 更大的自由 C 菜鸟的收获则是一遍一遍的检查
  • 复制文件夹所有内容 和 删除整个文件夹的2个函数

    选择自 hycapril 的 Blog 实现一个静态方法将指定文件夹下面的所有内容copy到目标文件夹下面 如果目标文件夹为只读属性就会报错 April 18April2005 In STU public static void CopyD
  • C++模板特例化

    模板是用来写一些独立化特定类型的代码 但是对于有些类型 在处理时 细节上却有所差别 常见的如char 如 现在你打算写一个栈 可以用于任何数据类型 那你肯定首先想到的就是模板啦 template
  • 类的构造函数和析构函数

    1 把对象的初始化工作放在构造函数中 把清除工作放在析构函数中 当对象被创建时 构造函数被自动执行 当对象消亡时 析构函数被自动执行 这下就不用担心忘了对象的初始化和清除工作 2 构造函数 析构函数与类同名 由于析构函数的目的与构造函数的相
  • 快速删除系统服务命令 sc delete ServiceName

    快速删除系统服务命令 sc delete ServiceName 要删除名为ServiceName的服务 可以用XP自带服务管理工具 SCP sc exe命令用以下命令即可删除指定的服务 sc delete ServiceName这些命令的
  • memset in C++ and C

    definition memset是计算机中C C 语言函数 将s所指向的某一块内存中的前n个 字节的内容全部设置为ch指定的ASCII值 第一个值为指定的内存地址 块的大小由第三个参数指定 这个函数通常为新申请的内存做初始化工作 其返回值
  • 拷贝构造函数和赋值构造函数声明为私有的作用

    转贴地址 http blog csdn net winer632 archive 2009 01 12 3762292 aspx 每个类只有一个赋值函数 由于并非所有的对象都会使用拷贝构造函数和赋值函数 程序员可能对这两个函数有些轻视 请先
  • C++面试题目集合(持续跟新)

    与我前面写的C语言进阶知识点遥相呼应 这才是C 面试 网上的面试题有些太简单了 C 面试题目最多集中在对象的内存模型 记住了 如果用c c 内存都不清楚 还写个屁的程序 1 C 的虚函数是怎样实现的 C 的虚函数使用了一个虚函数表来存放了每
  • 如何正确的关闭 MFC 线程

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

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

随机推荐

  • 神经网络中的epoch、batch、batch_size、iteration的理解

    神经网络中的epoch batch batch size iteration的理解 下面说说这三个区别 xff1a xff08 1 xff09 batchsize xff1a 批大小 在深度学习中 xff0c 一般采用SGD训练 xff0c
  • PyTorch源码解读(四)torchvision.models

    PyTorch框架中有一个非常重要且好用的包 xff1a torchvision xff0c 该包主要由3个子包组成 xff0c 分别是 xff1a torchvision datasets torchvision models torch
  • 3、AI的道德风险测试

    AI的道德性 AI系统的道德性如何保障是一个重要而复杂的问题 涉及到人工智能的发展 应用 监管 伦理 法律等多个方面 保障AI系统的道德性是一个很重要的问题 因为AI系统不仅会影响人类的生活和工作 也会涉及人类的价值观和伦理道德原则 针对这
  • 舵机单独供电时与单片机共地接线图

    转载 xff0c 原文链接为 xff1a https blog csdn net LUqingyaun article details 88600573 简单说 xff1a 外部电源两根线 xff0c VCC接舵机 xff0c GND接单片
  • 【正点原子】STM32开发板实验教程(F103)第52讲 I2C通信实验

    IIC通信 I2C xff08 IIC xff0c Inter Integrated Circuit xff09 xff0c 两线式串行总线 xff0c 由PHILIPS公司开发用于连接微控制器及外围设备 它是由数据线SDA和时钟线SCL构
  • 【解决方案】WSL2下rviz启动不了(Segmentation fault)

    好久没用WSL2了 xff0c 前几天使用了下 xff0c 发现rviz一直启动不了 出现以下问题 到处搜寻解决方案 xff0c 最后终于解决啦 xff01 xff01 参考文章 具体解决方案 首先启动rviz xff0c 出现上述问题 r
  • 【ROS】—— ROS通信机制——服务通信(三)

    文章目录 前言1 服务通信理论模型2 服务通信自定义srv2 1 定义srv文件2 2 编辑配置文件2 3 编译 3 服务通信自定义srv调用 C 43 43 3 1 vscode配置3 2 服务端3 3 客户端3 4 配置 CMakeLi
  • 【ROS】—— ROS常用组件_TF坐标变换_多态坐标变换与TF坐标变换实操(十一)

    文章目录 前言1 多态坐标变换1 1 发布方1 2 订阅方 C 43 43 1 3 订阅方 python 2 坐标系关系查看3 TF坐标变换实操 C 43 43 3 1准备3 2 生成新的乌龟3 3 增加键盘控制3 4 发布方 发布两只乌龟
  • 自动驾驶自主避障概况

    文章目录 前言1 自主避障在自动驾驶系统架构中的位置2 自主避障算法分类2 1 人工势场法 xff08 APF xff09 2 1 1引力势场的构建2 1 2斥力势场的构建2 1 3人工势场法的改进 2 2 TEB xff08 Timed
  • LQR算法基本原理

    LQR算法基本原理 前言 陈苏的最优控制 https www wendangwang com doc 1b3fea9be6dd6e50a295238b 3 文章是对陈苏的最优控制PPT的原理部分进行的简单总结 xff0c 若文中存在错误 x
  • <FreeRTOS入门第四节>其他相关API介绍

    提示 xff1a 文章写完后 xff0c 目录可以自动生成 xff0c 如何生成可参考右边的帮助文档 文章目录 前言一 API介绍 xff1f 二 API具体介绍1 UBaseType t uxTaskPriorityGet const T
  • <FreeRTOS入门第九节>事件标志位

    提示 xff1a 文章写完后 xff0c 目录可以自动生成 xff0c 如何生成可参考右边的帮助文档 文章目录 前言一 事件标志位是什么 xff1f 二 API介绍1 事件标志位的创建2 事件标志位的设置3 事件标志位清除4 获取某一位的事
  • 2019小结

    2019已经过去 xff0c 回想这过去的一年 xff0c 是近几年来最忙碌的一年 年初 xff0c 参加了一场读书会 xff0c 和公司同事一起读书 一本优秀的书是作者思想的精华 xff0c 通过读书可以认识和了解自己思维以外的世界 虽然
  • 4 AI系统的道德风险之歧视和不平等性的验证方法

    在人类社会中歧视和不平等是非常严重的问题 歧视和不平等会侵犯人的尊严和权利 阻碍社会的发展与和谐 根据联合国的相关资料描述 歧视和不平等的形势很多 包含种族歧视 性别歧视 地域歧视 宗教歧视 残疾歧视等等 打击歧视和不平等是全人类的共同责任
  • 继续前行

    很久没有更新日志了 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文件是程序的全局符号 源文件和代码行
  • “异常处理”学习小结

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

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