c++ 排查内存泄漏的妙招

前言

对于c++而言,如何查找内存泄漏是程序员亘古不变的话题;解决之道可谓花样繁多。因为最近要用到QT写程序,摆在我面前的第一个重要问题是内存防泄漏。如果能找到一个简单而行之有效的方法,对后续开发大有裨益。久思终得诀窍,本文就详细介绍我对此问题的应对之策。(文末符完整代码)

如何判断内存有泄漏

  内存分配和释放对应的操作是new、delete。如何判断内存是否释放干净?其实判断起来非常简单:一个独立的模块整个生存周期内new的个数和delete的个数相等。用伪代码标示如下:

int newCount = 0;
 int deleteCount = 0;

 //new 操作时
 new class();
 newCount++;

 //delete 操作时
 delete* objPtr;
 deleteCount++;

 //模块结束时
 if(newCount != deleteCount)
 {
 内存有泄漏
 }

如果对所有的new和delete操作,加上如上几行代码,就能发现是否有内存泄漏问题。如果采用上面方法解决问题,手段太low了。

我们的方法有如下特点:

1 使用起来超级简单,不增加开发难度。

2 发生内存泄漏时,能定位到具体是哪个类。

托管new delete 操作符

  要跟踪所有的new、delete操作,最简单的办法就是托管new、delete。不直接调用系统的操作符,而是用我们自己写的函数处理。在我们的函数内部,则别有洞天; 对new和delete的跟踪和记录就为我所欲也。托管new和delete需用到模板函数,代码如下:

class MemManage
{
 //单实例模式
private:
 static MemManage* _instance_ptr;
public:
 static MemManage* instance()
 {
 if (_instance_ptr == nullptr)
 {
  _instance_ptr = new MemManage();
 }
 return _instance_ptr;
 }

public:
 MemManage();

 //new操作 构造函数没有参数
 template <typename T>
 T* New()
 {
 ShowOperationMessage<T>(true);
 return new T();
 };

 //new操作 构造函数有1个参数
 template <typename T, typename TParam1>
 T* New(TParam1 param)
 {
 ShowOperationMessage<T>(true);
 return new T(param);
 };

 //new操作 构造函数有2个参数
 template <typename T, typename TParam1, typename TParam2>
 T* New(TParam1 param1, TParam2 param2)
 {
 ShowOperationMessage<T>(true);
 return new T(param1, param2);
 };

 //delete 操作
 template <typename T>
 void Delete(T t)
 {
 if (t == nullptr)
  return;

 ShowOperationMessage<T>(false);
 delete t;
 };

 //记录new delete
 template <typename T>
 void ShowOperationMessage(bool isNew)
 {
 //操作符对应的类名称
 const type_info& nInfo = typeid(T);
 QString className = nInfo.name();

 if (isNew)
 {
  _newCount++;
 }
 else
 {
  _deleteCount++;
 }

 if (!_showDetailMessage)
 {
  return;
 }

 if (isNew)
 {
  qDebug() << "*New" << className << ":" << _newCount << ":" << _deleteCount;
 }
 else
 {
  qDebug() << "Delete" << className << ":" << _newCount << ":" << _deleteCount;
 }
 }
}

如何使用辅助类

使用起来很简单,示例代码如下:

//*****new和delete使用伪代码

//new操作,需根据构造函数的参数个数调用对应的函数
//构造函数 没有参数
QFile* file = MemManage::instance()->New<QFile>();

//构造函数 有1个参数
QFile* file = MemManage::instance()->New<QFile, QString>("filename");

//构造函数 有2个参数
QFile* file = MemManage::instance()->New<QFile, QString,bool>("filename",true);

//delete 只有一种形式
MemManage::instance()->Delete(file);

一个模块调用周期结束 调用下列代码,查看是否有内存泄漏:

void ShowNewDelete(bool isShowDetail)
 {
 int leftNew = _newCount - _deleteCount;
 qDebug() << "***********************";
 qDebug() << "total New:" << _newCount << " Delete:" << _deleteCount << " leftNew:" << leftNew;
 }

 MemManage::instance()->ShowNewDelete(true);
 //debug输出如下,如果leftNew为0,则没内存泄漏
 total New : 166 Delete : 6 leftNew : 160

进一步定位内存泄漏问题

  通过判断new和delete的个数是否相等,只是知道了是否有内存泄漏;进一步定位问题,才能方便我们解决问题。如果能定位到操作哪一个类时,发生了内存泄漏,则问题范围就大大缩小。我们可以按类名,记录new和delete操作个数,c++获取类名函数如下:

const type_info &nInfo = typeid(T);
QString className = nInfo.name();

建立一个map表,记录类名对应的操作信息:

//每个类 统计的信息
class MemObjInfo
{
public:
 int NewCount = 0;
 int DeletCount = 0;
 QString ClassName;
};

//map对照表
QMap<QString, MemObjInfo*> _mapMemObjCount;

//按类名统计
void AddCount(QString& className, bool isNew)
{
 QMap<QString, MemObjInfo*>::ConstIterator i = _mapMemObjCount.find(className);
 if (i == _mapMemObjCount.constEnd())
 {
 MemObjInfo* info = new MemObjInfo();
 info->ClassName = className;
 if (isNew)
 {
  info->NewCount++;
 }
 else
 {
  info->DeletCount++;
 }
 _mapMemObjCount.insert(className, info);
 }
 else
 {
 MemObjInfo* info = i.value();
 if (isNew)
 {
  info->NewCount++;
 }
 else
 {
  info->DeletCount++;
 }
 }
}

如果有内存泄漏 则会输出如下信息:

如上图,对5个类的操作发送了内存泄漏。比如我们知道了类OfdDocumentPageAttr发生内存泄漏,就很容易定位问题了。

辅助类完整代码:

#ifndef MEMMANAGE_H
#define MEMMANAGE_H

#include <QDebug>
#include <QList>
#include <QMutex>

class LockRealse
{
public:
 LockRealse(QMutex* mutex)
 {
 _mutex = mutex;
 _mutex->lock();
 }

 ~LockRealse()
 {
 _mutex->unlock();
 }

private:
 QMutex* _mutex;
};

class MemObjInfo
{
public:
 int NewCount = 0;
 int DeletCount = 0;
 QString ClassName;
};

class MemManage
{
private:
 static MemManage* _instance_ptr;
public:
 static MemManage* instance()
 {
 if(_instance_ptr==nullptr)
 {
  _instance_ptr = new MemManage();
 }
 return _instance_ptr;
 }

public:
 MemManage()
 {
 _threadMutex = new QMutex();
 _newCount = 0;
 _deleteCount = 0;
 }

 template <typename T>
 T* New()
 {
 ShowOperationMessage<T>(true);
 return new T();
 };

 template <typename T,typename TParam1>
 T* New(TParam1 param)
 {
 ShowOperationMessage<T>(true);
 return new T(param);
 };

 template <typename T,typename TParam1,typename TParam2>
 T* New(TParam1 param1,TParam2 param2)
 {
 ShowOperationMessage<T>(true);
 return new T(param1,param2);
 };

 template <typename T>
 void Delete(T t)
 {
 if(t == nullptr)
  return;

 ShowOperationMessage<T>(false);
 delete t;
 };

 void ShowNewDelete(bool isShowDetail)
 {
 int leftNew = _newCount-_deleteCount;
 qDebug()<<"***********************";
 qDebug()<<"total New:"<<_newCount<<" Delete:"<<_deleteCount<<" leftNew:"<<leftNew;

 if(isShowDetail)
 {
  ShowNewDeleteDetail(false);
 }
 }

 void SetShowDetail(bool enable)
 {
 _showDetailMessage = enable;
 }

 template <typename T>
 void clearAndDelete(QList<T>& list)
 {
 foreach(T item ,list)
 {
  // Delete(item);
 }

 list.clear();
 };

private:
 template <typename T>
 void ShowOperationMessage(bool isNew)
 {
 LockRealse lock(_threadMutex);
 const type_info &nInfo = typeid(T);
 QString className = nInfo.name();
 className=TrimClassName(className);
 AddCount(className,isNew);

 if(isNew)
 {
  _newCount++;
 }
 else
 {
  _deleteCount++;
 }

 if(!_showDetailMessage)
 {
  return ;
 }

 if(isNew)
 {
  qDebug()<<"*New"<<className<<":"<<_newCount<<":"<<_deleteCount;
 }
 else
 {
  qDebug()<<"Delete"<<className<<":"<<_newCount<<":"<<_deleteCount;
 }
 }

 void AddCount(QString& className,bool isNew)
 {
 QMap<QString,MemObjInfo*>::ConstIterator i = _mapMemObjCount.find(className);
 if(i == _mapMemObjCount.constEnd())
 {
  MemObjInfo* info = new MemObjInfo();
  info->ClassName = className;
  if(isNew)
  {
  info->NewCount++;
  }
  else
  {
  info->DeletCount++;
  }
  _mapMemObjCount.insert(className,info);
 }
 else
 {
  MemObjInfo* info = i.value();
  if(isNew)
  {
  info->NewCount++;
  }
  else
  {
  info->DeletCount++;
  }
 }
 }

 void ShowNewDeleteDetail(bool isShowAll)
 {
 QMap<QString,MemObjInfo*>::ConstIterator i = _mapMemObjCount.cbegin();
 for(;i!=_mapMemObjCount.cend();i++)
 {
  MemObjInfo *info = i.value();
  int leftNew =info->NewCount-info->DeletCount ;
  if(leftNew!=0)
  {
  qDebug()<<"*** obj "<<info->ClassName<<" New:"<<info->NewCount
   <<" Delete:"<<info->DeletCount
   <<" Diff:"<<leftNew;
  }
  else
  {
  if(isShowAll)
  {
   qDebug()<<"obj "<<info->ClassName<<" New:"<<info->NewCount
    <<" Delete:"<<info->DeletCount
    <<" Diff:"<<leftNew;
  }
  }
 }
 }

 QString TrimClassName(QString& className)
 {
 int n= className.lastIndexOf(" *");
 if(n<0)
  return className.trimmed();

 return className.mid(0,n).trimmed();
 }

private:
 QMutex *_threadMutex;
 int _newCount;
 int _deleteCount;
 bool _showDetailMessage =false;

 QMap<QString,MemObjInfo*> _mapMemObjCount;
};

#endif // MEMMANAGE_H

后记

解决内存泄漏的方法很多。本文介绍了一种行之有效的方法。开发一个新项目前,就需确定如何跟踪定位内存泄漏,发现问题越早解决起来越简单。程序开发是循序渐进的过程,一个功能模块开发完成后,需及早确定是否有内存泄漏。防微杜渐,步步为营,方能产出高质量的产品。

以上就是c++ 防止内存泄漏的妙招的详细内容,更多关于c++ 防止内存泄漏的资料请关注我们其它相关文章!

(0)

相关推荐

  • C++程序检测内存泄漏的方法分享

    一.前言 在Linux平台上有valgrind可以非常方便的帮助我们定位内存泄漏,因为Linux在开发领域的使用场景大多是跑服务器,再加上它的开源属性,相对而言,处理问题容易形成"统一"的标准.而在Windows平台,服务器和客户端开发人员惯用的调试方法有很大不同.下面结合我的实际经验,整理下常见定位内存泄漏的方法. 注意:我们的分析前提是Release版本,因为在Debug环境下,通过VLD这个库或者CRT库本身的内存泄漏检测函数能够分析出内存泄漏,相对而言比较简单.而服务器有很多问

  • C++内存泄漏及检测工具详解

    首先我们需要知道程序有没有内存泄露,然后定位到底是哪行代码出现内存泄露了,这样才能将其修复. 最简单的方法当然是借助于专业的检测工具,比较有名如BoundsCheck,功能非常强大,相信做C++开发的人都离不开它.此外就是不使用任何工具,而是自己来实现对内存泄露的监控,分如下两种情况: 一. 在 MFC 中检测内存泄漏 假如是用MFC的程序的话,很简单.默认的就有内存泄露检测的功能. 我们用VS2005生成了一个MFC的对话框的程序,发现他可以自动的检测内存泄露.不用我们做任何特殊的操作. 仔细

  • C++中关于Crt的内存泄漏检测的分析介绍

    尽管这个概念已经让人说滥了 ,还是想简单记录一下, 以备以后查询. 复制代码 代码如下: #ifdef _DEBUG#define DEBUG_CLIENTBLOCK   new( _CLIENT_BLOCK, __FILE__, __LINE__)#else#define DEBUG_CLIENTBLOCK#endif#define _CRTDBG_MAP_ALLOC#include <crtdbg.h>#ifdef _DEBUG#define new DEBUG_CLIENTBLOCK#e

  • 详谈C++的内存泄漏问题

    1)有多少new就有多少delete.而且配对的new与delete要尽量在一个函数中.如果子函数中需要返回的数据是通过new来创建的,我的处理方式一般是在需要调用这个函数的位置将对应的数据规模创建好,并且通过指针或是引用传递到子函数中. 2)本人比较粗心大意,又一次在编码的过程中 竟然将delete语句放在了return语句的后面,导致程序运行的过程中会因内存不足二崩溃.检查了不下20遍才检查出来,真是哭的心情都有了.希望大家以我为戒,一定把return语句放在函数的最后面,估计也就我一个人犯

  • 如何通过wrap malloc定位C/C++的内存泄漏问题

    前言 用C/C++开发的程序执行效率很高,但却经常受到内存泄漏的困扰.本文提供一种通过wrap malloc查找memory leak的思路,依靠这个方法,笔者紧急解决了内存泄漏问题,避免项目流血上大促,该方法在日后工作中大放光彩,发现了项目中大量沉疴已久的内存泄漏问题. 什么是内存泄漏? 动态申请的内存丢失引用,造成没有办法回收它(我知道杠jing要说进程退出前系统会统一回收),这便是内存泄漏. Java等编程语言会自动管理内存回收,而C/C++需要显式的释放,有很多手段可以避免内存泄漏,比如

  • 基于malloc与free函数的实现代码及分析

    用于内存管理的malloc与free这对函数,对于使用C语言的程序员应该很熟悉.前段时间听说有的IT公司以"实现一个简单功能的malloc"作为面试题,正好最近在复习K&R,上面有所介绍,因此花了些时间仔细研究了一下.毕竟把题目做出来是次要的,了解实现思想.提升技术才是主要的.本文主要是对malloc与free实现思路的介绍,蓝色部分文字是在个人思考中觉得比较核心的东西:另外对于代码的说明,有一些K&R上的解释,使用绿色加亮. 在研究K&R第八章第五节的实现之前

  • C语言中的malloc使用详解

    一.原型:extern void *malloc(unsigned int num_bytes); 头文件:#include <malloc.h> 或 #include <alloc.h> (注意:alloc.h 与 malloc.h 的内容是完全一致的.) 功能:分配长度为num_bytes字节的内存块 说明:如果分配成功则返回指向被分配内存的指针,否则返回空指针NULL. 当内存不再使用时,应使用free()函数将内存块释放. 举例: #include<stdio.h&g

  • C++中new与delete、malloc与free应用分析

    一般来说,在C/C++的面试时,对于new/delete和malloc/free这两对的使用和区别经常被考查到,如果这种基础的问题都答不上来,估计很难过面试了.本文即是对new/delete和malloc/free这两对的使用和区别较为简单的分析一下,供大家参考. 一.new和delete new和delete是C++的运算符,用于动态分配内存和释放内存. 1.new表达式 标准库定义了operator new函数的几个重载版本,没有使用noexcept说明的版本在内存分配失败时可能会抛出bad

  • c++ 排查内存泄漏的妙招

    前言 对于c++而言,如何查找内存泄漏是程序员亘古不变的话题:解决之道可谓花样繁多.因为最近要用到QT写程序,摆在我面前的第一个重要问题是内存防泄漏.如果能找到一个简单而行之有效的方法,对后续开发大有裨益.久思终得诀窍,本文就详细介绍我对此问题的应对之策.(文末符完整代码) 如何判断内存有泄漏 内存分配和释放对应的操作是new.delete.如何判断内存是否释放干净?其实判断起来非常简单:一个独立的模块整个生存周期内new的个数和delete的个数相等.用伪代码标示如下: int newCoun

  • 手把手教你如何排查Javascript内存泄漏

    目录 引言 如何判断我的应用发生了内存泄漏 Performance和Memory都可以用来定位内存问题,先用谁呢 通过Memory面板定位内存泄漏的流程通常是怎么样的呢 为什么我的内存快照记录下来之后看不懂,还出现了很多奇怪的变量 快照里有一些“Detached DOM tree”,是什么意思 Shallow size 和 Retained size,它们有什么不同 Memory里的Summary视图, Comparison视图, Dominators视图和Containment视图分别有什么不

  • Android Studio 3.0上分析内存泄漏的原因

    以前用eclipse的时候,我们采用的是DDMS和MAT,不仅使用步骤复杂繁琐,而且要手动排查内存泄漏的位置,操作起来比较麻烦.后来随着Android studio的潮流,我也抛弃了eclipse加入了AS. Android Studio也开始支持自动进行内存泄漏检查,并且操作起来也比较方便. 封面 戳我下载 Android Studio 3.0 这个不用梯子我会告诉你吗 1.写在前面 Google在上周发布了Android Studio 3.0的正式版本,周四早晨在上班的地铁上就看到群里在沸沸

  • 浅谈VueJS SSR 后端绘制内存泄漏的相关解决经验

    引言 Memory Leak 是最难排查调试的 Bug 种类之一,因为内存泄漏是个 undecidable problem,只有开发者才能明确一块内存是不是需要被回收.再加上内存泄漏也没有特定的报错信息,只能通过一定时间段的日志来判断是否存在内存泄漏.大家熟悉的常用调试工具对排查内存泄漏也没有用武之地.当然了,除了专门用于排查内存泄漏的工具(抓取Heap之类的工具)之外. 对于不同的语言,各种排查内存泄漏的方式方法也不尽相同.对于 JavaScript 来说,针对不同的平台,调试工具也是不一样的

  • Android内存泄漏排查利器LeakCanary

    本文为大家分享了Android内存泄漏排查利器,供大家参考,具体内容如下 开源地址:https://github.com/square/leakcanary 在 build.gralde 里加上依赖, 然后sync 一下, 添加内容如下 dependencies { .... debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5' releaseCompile 'com.squareup.leakcanary:leakcanar

  • 一次 Java 内存泄漏的排查解决过程详解

    由来 前些日子小组内安排值班,轮流看顾我们的服务,主要做一些报警邮件处理.Bug 排查.运营 issue 处理的事.工作日还好,无论干什么都要上班的,若是轮到周末,那这一天算是毁了. 不知道是公司网络广了就这样还是网络运维组不给力,网络总有问题,不是这边交换机脱网了就是那边路由器坏了,还偶发地各种超时,而我们灵敏地服务探测服务总能准确地抓住偶现的小问题,给美好的工作加点料.好几次值班组的小伙伴们一起吐槽,商量着怎么避过服务保活机制,偷偷停了探测服务而不让人发现(虽然也并不敢). 前些天我就在周末

  • 排查Java应用内存泄漏问题的步骤

    什么是内存泄漏 内存泄漏是指java应用的堆内存使用率持续升高,直至内存溢出. 内存泄漏的的原因可能有多种 分配给应用程序的内存本身过小.而应用的业务代码,确实需要生成大量的对象 代码bug,某些需要被回收的对象,由于代码bug,却持续的被引用,导致java虚拟机无法回收这些对象.从而撑爆内存 无论哪种内存泄露,我们的解决方法都是要定位到具体是什么对象,占用了大量内存,从而方便我们基于此进行代码分析,debug,找出代码问题. 而能够帮助我们实现这一目的的方式就是获取java应用的内存 dump

  • C++之内存泄漏排查详解

    目录 一 .经验排查 二 .使用Visual Leak Detector for Visual C++ 2.1 Visual Leak Detector for Visual C++简介 2.2 Visual Leak Detector源码获取编译 2.2.1 源码获取,相关git地址 2.2.2 发布版本获取 2.2.3 进行编译 2.2.4 自带gtest工程测试 2.3 如何测试自己的项目呢 2.3.1 配置工程 2.3.2 编写简单的测试用例 2.3.3 检测结果如图 三.总结 一 .经

  • 一篇文章教你如何排查.NET内存泄漏

    目录 前言 检查托管内存使用 生成dump文件 分析 core dump 总结 前言 内存泄漏通常表示:一个应用程序的某些对象在完成它的的生命周期后,由于它被其他对象意外引用,导致后续gc无法对它进行回收,长此以往就会导致程序性能的下降以及潜在的 OutOfMemoryException. 这篇我们通过一个内存泄漏工具对 .NET Core 程序进行内存泄漏分析,如果程序是跑在windows上,那直接可以使用 Visual Studio 进行诊断. 检查托管内存使用 在开始分析内存泄漏之前,你一

  • python内存泄漏排查技巧总结

    目录 思路一:研究新旧源码及二方库依赖差异 思路二:监测新旧版本内存变化差异 问题所在 进阶思路 1.使用objgraph工具 2.使用pympler工具 首先搞清楚了本次问题的现象: 1. 服务在13号上线过一次,而从23号开始,出现内存不断攀升问题,达到预警值重启实例后,攀升速度反而更快. 2. 服务分别部署在了A.B 2种芯片上,但除模型推理外,几乎所有的预处理.后处理共享一套代码.而B芯片出现内存泄漏警告,A芯片未出现任何异常. 思路一:研究新旧源码及二方库依赖差异 根据以上两个条件,首

随机推荐