前言
C++編碼中Memory Leak是一個很討厭卻又揮之不去的話題,最近由於引入了GC,爲了驗證GC是否確實正常free了內存,於是先提供了一個內存分配的Tracer。
與分配器不同,分配器主要解決的是兩個問題:
1、性能,池式分配往往能提供比直接Virtual Allocation快得多的效能。據說這一原則在Vista後無效了,因爲微軟修改了VA的實現機制,只是聽說,沒有實際測試過。
2、碎片,避免大量散內存分配衝散了本身連續的內存,導致後面內存因爲沒有連續區塊而分配不出來。
我們的跟蹤器Tracer主要是想解決一個問題,就是啥時候分了內存,啥時候刪的,程序退出時刪除掉沒。
方案一:DEBUG_NEW方案
基本上,這個主題之前也有很多前輩都寫過了,這裏也沒有超越前輩們的什麼方案,只是自己做這個模塊時的心得和理解。
這個問題有兩個比較成型的方案,一個就是MFC的DEBUG_NEW方案,MAX SDK用的也是這個方案。
其實原理很簡單,我們只要能獲取到當前語句的文件名和行號,然後new的時候,我們讓我們的Tracer記錄一下當前的地址,並與文件名和行號綁定,然後,delete的時候,按照地址來去掉這條記錄即可。
實現起來如何實現呢?
這個問題無非是要解決兩個問題,首先,new這個東西,我們需要接管下來,接管下來後才能記錄我們自己的信息。
C++的operator new是可以有不同形式的重載的,比如:
void* operator new (size_tInSize, const char* InLogMsg)
{
std::cout << InLogMsg <<std::endl;
return ::malloc(InSize);
}
調用這個重載時,new就要這麼調了:
int* p = new ("我正在分配內存") int;
注意,new和operator new不是一回事兒,而提供了特殊形式的operator new後,需要相應地提供類似的operator delete,否則會有Level 1 warning。
對這個問題有興趣可見本文最後的補充1,它與本文的主題無關,暫時無視。
第二個問題是,我們如何獲知當前語句的文件和航好呢?
C++可以用下面的方法來取得當前語句所在的文件和行號:
std::cout << "當前文件爲:" <<__FILE__ <<"。當前行號爲:" << __LINE__<<std::endl;
準備工作齊活兒了,準備開始吧!
首先,我們需要提供一個Tracer來記錄文件名和行號這些信息:
class TracerFileLn
{
public:
TracerFileLn& singleton();
private:
struct _AllocInfo
{
const char*filename_;
size_tfileLn_;
};
typedefstd::hash_map<qword, _AllocInfo>alloc_hash;
alloc_hashallocInfoHash_;
public:
void traceMalloc(void* InPtr, const char* InFilename, size_t InFileLn)
{
_AllocInfosAllocInfo = { InFilename, InFileLn };
allocInfoHash_.insert( alloc_hash::pair((qword)InPtr, sAllocInfo) );
}
void traceFree(void* InPtr)
{
auto it = allocInfoHash_.find(InPtr);
allocInfoHash_.erase(it);
}
};
所以,我們能不能提供下面這個new的重載呢?
void* operator new(size_tInSize,const char* InFilename,size_t InFileLn)
然後,operator new這麼實現:
void* operator new(size_t InSize, const char* InFilename, size_t InFileLn)
{
void* pPtr = ::malloc(InSize);
TracerFileLn::singleton().traceMalloc(pPtr, InFilename, InFileLn);
return pPtr;
}
然後,operator delete需要這麼實現:
void operator delete(void* InPtr)
{
TracerFileLn::singleton().traceFree(InPtr);
}
void operator delete(void* InPtr, const char* InFilename,size_t InFileLn)
{
TracerFileLn::singleton().traceFree(InPtr);
}
記得 new[] 和 delete[] 也要做相應的實現。
但這樣的話,咱們就不能再使用C++的原生new了,而是必須要用新的new。
int * pPtr = new(__FILE__,__LINE__)int;
所有使用new的地方都要這麼寫,太麻煩了?不過這難不倒我們,有宏呢,就跟MFC DEBUG_NEW那樣:
#define DEBUG_NEW new (__FILE__,__LINE__)
#define new DEBUG_NEW
然後再用new 的地方實際上就會被替換爲new (__FILE__,__LINE__) 了。
int * pPtr = newint;
繼續這麼寫就可以,只是在這句話之前,必須要確保其前面有#define new DEBUG_NEW的宏聲明。
信息Trace下來了,程序結束後,只需要看hash裏還有哪些AllocInfo,一個個Dump出來就可以查到相應的內存泄露了。
看起來很方便吧?方不方便,後面還會繼續展開,敬請期待。