簡單的Memory leak跟蹤(一) DEBUG_NEW方案

來源:互聯網
上載者:User
前言

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出來就可以查到相應的記憶體泄露了。

看起來很方便吧?方不方便,後面還會繼續展開,敬請期待。

補充1:提供新格式的operator new後,為何要提供相應的operator delete呢?因為C++標準規定,object的構造是可以有異常的,如果構造有異常,那麼當前object就應該被回收。如果你對這個object的new使用了自訂格式,那麼在建構函式異常時,C++回收object也會使用對應自訂格式的delete,所以相應的operator delete一定要提供,否則這種情況下記憶體就不會回收了。\但如果一切正常,手動調用delete時,調用的是哪個delete呢?答案是標準的delete:void operator delete(void* InPtr);另外,new 和 operator new 不是一回事兒?是的,new是C++關鍵字,new做的事情是什麼呢?1,調用對應形式的operator new,分配記憶體。2,調用placement new,也就是對象的建構函式,構造對象。也就是new有兩步,第一步是operator new,operator new只管分配記憶體,不管別的。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.