c++ 排查內(nèi)存泄漏的妙招
前言
對(duì)于c++而言,如何查找內(nèi)存泄漏是程序員亙古不變的話題;解決之道可謂花樣繁多。因?yàn)樽罱玫絈T寫程序,擺在我面前的第一個(gè)重要問(wèn)題是內(nèi)存防泄漏。如果能找到一個(gè)簡(jiǎn)單而行之有效的方法,對(duì)后續(xù)開(kāi)發(fā)大有裨益。久思終得訣竅,本文就詳細(xì)介紹我對(duì)此問(wèn)題的應(yīng)對(duì)之策。(文末符完整代碼)
如何判斷內(nèi)存有泄漏
內(nèi)存分配和釋放對(duì)應(yīng)的操作是new、delete。如何判斷內(nèi)存是否釋放干凈?其實(shí)判斷起來(lái)非常簡(jiǎn)單:一個(gè)獨(dú)立的模塊整個(gè)生存周期內(nèi)new的個(gè)數(shù)和delete的個(gè)數(shù)相等。用偽代碼標(biāo)示如下:
int newCount = 0;
int deleteCount = 0;
//new 操作時(shí)
new class();
newCount++;
//delete 操作時(shí)
delete* objPtr;
deleteCount++;
//模塊結(jié)束時(shí)
if(newCount != deleteCount)
{
內(nèi)存有泄漏
}
如果對(duì)所有的new和delete操作,加上如上幾行代碼,就能發(fā)現(xiàn)是否有內(nèi)存泄漏問(wèn)題。如果采用上面方法解決問(wèn)題,手段太low了。
我們的方法有如下特點(diǎn):
1 使用起來(lái)超級(jí)簡(jiǎn)單,不增加開(kāi)發(fā)難度。
2 發(fā)生內(nèi)存泄漏時(shí),能定位到具體是哪個(gè)類。
托管new delete 操作符
要跟蹤所有的new、delete操作,最簡(jiǎn)單的辦法就是托管new、delete。不直接調(diào)用系統(tǒng)的操作符,而是用我們自己寫的函數(shù)處理。在我們的函數(shù)內(nèi)部,則別有洞天; 對(duì)new和delete的跟蹤和記錄就為我所欲也。托管new和delete需用到模板函數(shù),代碼如下:
class MemManage
{
//單實(shí)例模式
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ù)沒(méi)有參數(shù)
template <typename T>
T* New()
{
ShowOperationMessage<T>(true);
return new T();
};
//new操作 構(gòu)造函數(shù)有1個(gè)參數(shù)
template <typename T, typename TParam1>
T* New(TParam1 param)
{
ShowOperationMessage<T>(true);
return new T(param);
};
//new操作 構(gòu)造函數(shù)有2個(gè)參數(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)
{
//操作符對(duì)應(yīng)的類名稱
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;
}
}
}
如何使用輔助類
使用起來(lái)很簡(jiǎn)單,示例代碼如下:
//*****new和delete使用偽代碼
//new操作,需根據(jù)構(gòu)造函數(shù)的參數(shù)個(gè)數(shù)調(diào)用對(duì)應(yīng)的函數(shù)
//構(gòu)造函數(shù) 沒(méi)有參數(shù)
QFile* file = MemManage::instance()->New<QFile>();
//構(gòu)造函數(shù) 有1個(gè)參數(shù)
QFile* file = MemManage::instance()->New<QFile, QString>("filename");
//構(gòu)造函數(shù) 有2個(gè)參數(shù)
QFile* file = MemManage::instance()->New<QFile, QString,bool>("filename",true);
//delete 只有一種形式
MemManage::instance()->Delete(file);
一個(gè)模塊調(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,則沒(méi)內(nèi)存泄漏
total New : 166 Delete : 6 leftNew : 160
進(jìn)一步定位內(nèi)存泄漏問(wèn)題
通過(guò)判斷new和delete的個(gè)數(shù)是否相等,只是知道了是否有內(nèi)存泄漏;進(jìn)一步定位問(wèn)題,才能方便我們解決問(wèn)題。如果能定位到操作哪一個(gè)類時(shí),發(fā)生了內(nèi)存泄漏,則問(wèn)題范圍就大大縮小。我們可以按類名,記錄new和delete操作個(gè)數(shù),c++獲取類名函數(shù)如下:
const type_info &nInfo = typeid(T); QString className = nInfo.name();
建立一個(gè)map表,記錄類名對(duì)應(yīng)的操作信息:
//每個(gè)類 統(tǒng)計(jì)的信息
class MemObjInfo
{
public:
int NewCount = 0;
int DeletCount = 0;
QString ClassName;
};
//map對(duì)照表
QMap<QString, MemObjInfo*> _mapMemObjCount;
//按類名統(tǒng)計(jì)
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)存泄漏 則會(huì)輸出如下信息:

如上圖,對(duì)5個(gè)類的操作發(fā)送了內(nèi)存泄漏。比如我們知道了類OfdDocumentPageAttr發(fā)生內(nèi)存泄漏,就很容易定位問(wèn)題了。
輔助類完整代碼:
#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)存泄漏的方法很多。本文介紹了一種行之有效的方法。開(kāi)發(fā)一個(gè)新項(xiàng)目前,就需確定如何跟蹤定位內(nèi)存泄漏,發(fā)現(xiàn)問(wèn)題越早解決起來(lái)越簡(jiǎn)單。程序開(kāi)發(fā)是循序漸進(jìn)的過(guò)程,一個(gè)功能模塊開(kāi)發(fā)完成后,需及早確定是否有內(nèi)存泄漏。防微杜漸,步步為營(yíng),方能產(chǎn)出高質(zhì)量的產(chǎn)品。
以上就是c++ 防止內(nèi)存泄漏的妙招的詳細(xì)內(nèi)容,更多關(guān)于c++ 防止內(nèi)存泄漏的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
C++中棧結(jié)構(gòu)建立與操作詳細(xì)解析
我們可以把棧理解成一個(gè)大倉(cāng)庫(kù),放在倉(cāng)庫(kù)門口(棧頂)的貨物會(huì)優(yōu)先被取出,然后再取出里面的貨物。而從數(shù)據(jù)的邏輯結(jié)構(gòu)來(lái)看,棧結(jié)構(gòu)起始就是一種線性結(jié)構(gòu)2013-10-10
C++深入刨析優(yōu)先級(jí)隊(duì)列priority_queue的使用
最近我學(xué)習(xí)了C++中的STL庫(kù)中的優(yōu)先級(jí)隊(duì)列(priority_queue)容器適配器,對(duì)于優(yōu)先級(jí)隊(duì)列,我們不僅要會(huì)使用常用的函數(shù)接口,我們還有明白這些接口在其底層是如何實(shí)現(xiàn)的2022-08-08
基于C++11實(shí)現(xiàn)手寫線程池的示例代碼
在實(shí)際的項(xiàng)目中,使用線程池是非常廣泛的,本文主要介紹了基于C++11實(shí)現(xiàn)手寫線程池的示例代碼,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2024-08-08
C語(yǔ)言中回調(diào)函數(shù)的含義與使用場(chǎng)景詳解(2)
這篇文章主要為大家詳細(xì)介紹了C語(yǔ)言中回調(diào)函數(shù)的含義與使用場(chǎng)景,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來(lái)幫助2022-03-03
C語(yǔ)言超詳細(xì)講解棧的實(shí)現(xiàn)及代碼
棧(stack)又名堆棧,它是一種運(yùn)算受限的線性表。限定僅在表尾進(jìn)行插入和刪除操作的線性表。這一端被稱為棧頂,相對(duì)地,把另一端稱為棧底。向一個(gè)棧插入新元素又稱作進(jìn)棧、入?;驂簵?,它是把新元素放到棧頂元素的上面,使之成為新的棧頂元素2022-04-04
C/C++使用C語(yǔ)言實(shí)現(xiàn)多態(tài)
這篇文章主要介紹了C/C++多態(tài)的實(shí)現(xiàn)機(jī)制理解的相關(guān)資料,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下,希望能給你帶來(lái)幫助2021-08-08

