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

这篇文章主要介绍了c++ 如何用辅助类排查内存泄漏,帮助大家更好的理解和学习使用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  T* New() { ShowOperationMessage(true); return new T(); }; //new操作 构造函数有1个参数 template  T* New(TParam1 param) { ShowOperationMessage(true); return new T(param); }; //new操作 构造函数有2个参数 template  T* New(TParam1 param1, TParam2 param2) { ShowOperationMessage(true); return new T(param1, param2); }; //delete 操作 template  void Delete(T t) { if (t == nullptr) return; ShowOperationMessage(false); delete t; }; //记录new delete template  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(); //构造函数 有1个参数 QFile* file = MemManage::instance()->New("filename"); //构造函数 有2个参数 QFile* file = MemManage::instance()->New("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 _mapMemObjCount; //按类名统计 void AddCount(QString& className, bool isNew) { QMap::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  #include  #include  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  T* New() { ShowOperationMessage(true); return new T(); }; template  T* New(TParam1 param) { ShowOperationMessage(true); return new T(param); }; template  T* New(TParam1 param1,TParam2 param2) { ShowOperationMessage(true); return new T(param1,param2); }; template  void Delete(T t) { if(t == nullptr) return; ShowOperationMessage(false); delete t; }; void ShowNewDelete(bool isShowDetail) { int leftNew = _newCount-_deleteCount; qDebug()<<"***********************"; qDebug()<<"total New:"<<_newCount<<" Delete:"<<_deleteCount<<" leftNew:"< void clearAndDelete(QList& list) { foreach(T item ,list) { // Delete(item); } list.clear(); }; private: template  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"<::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::ConstIterator i = _mapMemObjCount.cbegin(); for(;i!=_mapMemObjCount.cend();i++) { MemObjInfo *info = i.value(); int leftNew =info->NewCount-info->DeletCount ; if(leftNew!=0) { qDebug()<<"*** obj "<ClassName<<" New:"<NewCount <<" Delete:"<DeletCount <<" Diff:"<ClassName<<" New:"<NewCount <<" Delete:"<DeletCount <<" Diff:"< _mapMemObjCount; }; #endif // MEMMANAGE_H

后记 

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

以上就是c++ 排查内存泄漏的妙招的详细内容,更多请关注0133技术站其它相关文章!

赞(0) 打赏
未经允许不得转载:0133技术站首页 » C语言