c++ 排查內存泄漏的妙招
前言
對于c++而言,如何查找內存泄漏是程序員亙古不變的話題;解決之道可謂花樣繁多。因為最近要用到QT寫程序,擺在我面前的第一個重要問題是內存防泄漏。如果能找到一個簡單而行之有效的方法,對后續(xù)開發(fā)大有裨益。久思終得訣竅,本文就詳細介紹我對此問題的應對之策。(文末符完整代碼)
如何判斷內存有泄漏
內存分配和釋放對應的操作是new、delete。如何判斷內存是否釋放干凈?其實判斷起來非常簡單:一個獨立的模塊整個生存周期內new的個數(shù)和delete的個數(shù)相等。用偽代碼標示如下:
int newCount = 0;
int deleteCount = 0;
//new 操作時
new class();
newCount++;
//delete 操作時
delete* objPtr;
deleteCount++;
//模塊結束時
if(newCount != deleteCount)
{
內存有泄漏
}
如果對所有的new和delete操作,加上如上幾行代碼,就能發(fā)現(xiàn)是否有內存泄漏問題。如果采用上面方法解決問題,手段太low了。
我們的方法有如下特點:
1 使用起來超級簡單,不增加開發(fā)難度。
2 發(fā)生內存泄漏時,能定位到具體是哪個類。
托管new delete 操作符
要跟蹤所有的new、delete操作,最簡單的辦法就是托管new、delete。不直接調用系統(tǒng)的操作符,而是用我們自己寫的函數(shù)處理。在我們的函數(shù)內部,則別有洞天; 對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操作 構造函數(shù)沒有參數(shù)
template <typename T>
T* New()
{
ShowOperationMessage<T>(true);
return new T();
};
//new操作 構造函數(shù)有1個參數(shù)
template <typename T, typename TParam1>
T* New(TParam1 param)
{
ShowOperationMessage<T>(true);
return new T(param);
};
//new操作 構造函數(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操作,需根據構造函數(shù)的參數(shù)個數(shù)調用對應的函數(shù)
//構造函數(shù) 沒有參數(shù)
QFile* file = MemManage::instance()->New<QFile>();
//構造函數(shù) 有1個參數(shù)
QFile* file = MemManage::instance()->New<QFile, QString>("filename");
//構造函數(shù) 有2個參數(shù)
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的個數(shù)是否相等,只是知道了是否有內存泄漏;進一步定位問題,才能方便我們解決問題。如果能定位到操作哪一個類時,發(fā)生了內存泄漏,則問題范圍就大大縮小。我們可以按類名,記錄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++;
}
}
}
如果有內存泄漏 則會輸出如下信息:

如上圖,對5個類的操作發(fā)送了內存泄漏。比如我們知道了類OfdDocumentPageAttr發(fā)生內存泄漏,就很容易定位問題了。
輔助類完整代碼:
#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
后記
解決內存泄漏的方法很多。本文介紹了一種行之有效的方法。開發(fā)一個新項目前,就需確定如何跟蹤定位內存泄漏,發(fā)現(xiàn)問題越早解決起來越簡單。程序開發(fā)是循序漸進的過程,一個功能模塊開發(fā)完成后,需及早確定是否有內存泄漏。防微杜漸,步步為營,方能產出高質量的產品。
以上就是c++ 防止內存泄漏的妙招的詳細內容,更多關于c++ 防止內存泄漏的資料請關注腳本之家其它相關文章!
相關文章
C++深入刨析優(yōu)先級隊列priority_queue的使用
最近我學習了C++中的STL庫中的優(yōu)先級隊列(priority_queue)容器適配器,對于優(yōu)先級隊列,我們不僅要會使用常用的函數(shù)接口,我們還有明白這些接口在其底層是如何實現(xiàn)的2022-08-08

