c++ 排查內(nèi)存泄漏的妙招
前言
對于c++而言,如何查找內(nèi)存泄漏是程序員亙古不變的話題;解決之道可謂花樣繁多。因為最近要用到QT寫程序,擺在我面前的第一個重要問題是內(nèi)存防泄漏。如果能找到一個簡單而行之有效的方法,對后續(xù)開發(fā)大有裨益。久思終得訣竅,本文就詳細介紹我對此問題的應對之策。(文末符完整代碼)
如何判斷內(nèi)存有泄漏
內(nèi)存分配和釋放對應的操作是new、delete。如何判斷內(nèi)存是否釋放干凈?其實判斷起來非常簡單:一個獨立的模塊整個生存周期內(nèi)new的個數(shù)和delete的個數(shù)相等。用偽代碼標示如下:
int newCount = 0; int deleteCount = 0; //new 操作時 new class(); newCount++; //delete 操作時 delete* objPtr; deleteCount++; //模塊結(jié)束時 if(newCount != deleteCount) { 內(nèi)存有泄漏 }
如果對所有的new和delete操作,加上如上幾行代碼,就能發(fā)現(xiàn)是否有內(nèi)存泄漏問題。如果采用上面方法解決問題,手段太low了。
我們的方法有如下特點:
1 使用起來超級簡單,不增加開發(fā)難度。
2 發(fā)生內(nèi)存泄漏時,能定位到具體是哪個類。
托管new delete 操作符
要跟蹤所有的new、delete操作,最簡單的辦法就是托管new、delete。不直接調(diào)用系統(tǒng)的操作符,而是用我們自己寫的函數(shù)處理。在我們的函數(shù)內(nèi)部,則別有洞天; 對new和delete的跟蹤和記錄就為我所欲也。托管new和delete需用到模板函數(shù),代碼如下:
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操作 構(gòu)造函數(shù)沒有參數(shù) template <typename T> T* New() { ShowOperationMessage<T>(true); return new T(); }; //new操作 構(gòu)造函數(shù)有1個參數(shù) template <typename T, typename TParam1> T* New(TParam1 param) { ShowOperationMessage<T>(true); return new T(param); }; //new操作 構(gòu)造函數(shù)有2個參數(shù) 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操作,需根據(jù)構(gòu)造函數(shù)的參數(shù)個數(shù)調(diào)用對應的函數(shù) //構(gòu)造函數(shù) 沒有參數(shù) QFile* file = MemManage::instance()->New<QFile>(); //構(gòu)造函數(shù) 有1個參數(shù) QFile* file = MemManage::instance()->New<QFile, QString>("filename"); //構(gòu)造函數(shù) 有2個參數(shù) QFile* file = MemManage::instance()->New<QFile, QString,bool>("filename",true); //delete 只有一種形式 MemManage::instance()->Delete(file);
一個模塊調(diào)用周期結(jié)束 調(diào)用下列代碼,查看是否有內(nèi)存泄漏:
void ShowNewDelete(bool isShowDetail) { int leftNew = _newCount - _deleteCount; qDebug() << "***********************"; qDebug() << "total New:" << _newCount << " Delete:" << _deleteCount << " leftNew:" << leftNew; } MemManage::instance()->ShowNewDelete(true); //debug輸出如下,如果leftNew為0,則沒內(nèi)存泄漏 total New : 166 Delete : 6 leftNew : 160
進一步定位內(nèi)存泄漏問題
通過判斷new和delete的個數(shù)是否相等,只是知道了是否有內(nèi)存泄漏;進一步定位問題,才能方便我們解決問題。如果能定位到操作哪一個類時,發(fā)生了內(nèi)存泄漏,則問題范圍就大大縮小。我們可以按類名,記錄new和delete操作個數(shù),c++獲取類名函數(shù)如下:
const type_info &nInfo = typeid(T); QString className = nInfo.name();
建立一個map表,記錄類名對應的操作信息:
//每個類 統(tǒng)計的信息 class MemObjInfo { public: int NewCount = 0; int DeletCount = 0; QString ClassName; }; //map對照表 QMap<QString, MemObjInfo*> _mapMemObjCount; //按類名統(tǒng)計 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++; } } }
如果有內(nèi)存泄漏 則會輸出如下信息:
如上圖,對5個類的操作發(fā)送了內(nèi)存泄漏。比如我們知道了類OfdDocumentPageAttr發(fā)生內(nèi)存泄漏,就很容易定位問題了。
輔助類完整代碼:
#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
后記
解決內(nèi)存泄漏的方法很多。本文介紹了一種行之有效的方法。開發(fā)一個新項目前,就需確定如何跟蹤定位內(nèi)存泄漏,發(fā)現(xiàn)問題越早解決起來越簡單。程序開發(fā)是循序漸進的過程,一個功能模塊開發(fā)完成后,需及早確定是否有內(nèi)存泄漏。防微杜漸,步步為營,方能產(chǎn)出高質(zhì)量的產(chǎn)品。
以上就是c++ 防止內(nèi)存泄漏的妙招的詳細內(nèi)容,更多關于c++ 防止內(nèi)存泄漏的資料請關注腳本之家其它相關文章!
相關文章
簡單對比C語言中的fputs()函數(shù)和fputc()函數(shù)
這篇文章主要介紹了簡單對比C語言中的fputs()函數(shù)和fputc()函數(shù),注意其之間的區(qū)別,需要的朋友可以參考下2015-08-08