2012-10-10 600 views
6

我想打印在哪一行AddRef和Release是called.Here是代碼檢測內存泄漏計算對象

在下面的代碼我已經在ReferenceCount類,其主要功能,以增加和減少refernce計數創建。 Referencemanager類跟蹤引用計數,並在對象達到0時刪除對象。

Test1是測試類。主要我創建Test1指針並用CReferenceManager類包裝它。現在在創建CReferenceManager類的過程中,AddRef被調用,而銷燬Release將被調用。

如果存在內存泄漏,那麼當AddRef和Release在該點引用計數時可以打印出FILE和LINE數字將更容易檢測。

如果有方法可以打印AddRef和Release被調用的FILE和LINE號碼。一種方法是,我可以覆蓋AddRef和在派生​​類和prinf文件和行號

//ReferenceCount.h 
#include <string> 
#include <Windows.h> 

using namespace std; 
class CReferenceCount 
{ 
public: 
    CReferenceCount(); 
    virtual ~CReferenceCount(); 
    virtual void AddRef(); 
    virtual bool Release(); 


private: 
    LONG m_ref; 

}; 


// RefCount.cpp 
// 

#include "stdafx.h" 
#include "ReferenceCount.h" 


CReferenceCount::CReferenceCount():m_ref(0) 
{ 
    AddRef(); 

} 

CReferenceCount::~CReferenceCount() 
{ 
} 

void CReferenceCount::AddRef() 
{ 
    InterlockedIncrement(&m_ref); 
} 

bool CReferenceCount::Release() 
{ 
    if (InterlockedDecrement(&m_ref) == 0) 
    { 
     delete this; 
     return true; 
    } 

    return false; 
} 



//ReferenceManager.h 
#include <string> 
#include <Windows.h> 

using namespace std; 
class CReferenceCount 
{ 
public: 
    CReferenceCount(); 
    virtual ~CReferenceCount(); 
    virtual void AddRef(); 
    virtual bool Release(); 


private: 
    LONG m_ref; 

}; 

//test.cpp 
#include "stdafx.h" 
#include "ReferenceCount.h" 
#include "RefManager.h" 
#include <iostream> 
using namespace std; 

class Test1: public CReferenceCount 
{ 
public: 
    Test1(){} 
    ~Test1(){} 

private : 
    int m_i; 
}; 

void main() 
{ 
    Test1 *pTest= new Test1(); 
    CReferenceManager<Test1> testRef(pTest); 

} 

Similare問題放開我已經發布 finding who creates object via smart pointer Design pattern to detect memory leaks for reference counted smart pointers

但非的答案給解決這個正確的解釋有問題,

+9

'刪除此;'OMG !!! –

+0

你使用智能指針調用AddRef/Release還是你手動調用它們?如果你手動給他們打電話,我強烈反對這個建議。 –

+0

模板將是更好的解決方案;可以使模板適合每個對象,就像一個std :: shared_ptr –

回答

6

唯一的方法是定義調用AddRef和Release的宏,因爲函數無法從內部知道它們被調用的位置。所以你可以使用類似的東西。

#define RELEASE(obj) cout << __LINE__ << ":" << __FILE__ << endl; (obj).Release(); 

另外,不同的編譯器有不同的預定義的宏;如果可移植性是一個問題,那麼在編寫如上所述的代碼時應該考慮一下。 MSDN reference (2003)

鑑於您的評論如下,我可能會提供另一個有點hackish的解決方案。您可能無法看到引用的發佈位置,但您可以獲取有關它的創建位置以及哪些未正確發佈的更多信息。

template <typename T> 
struct CReferenceManager 
{ 
    CReferenceManager(const T & _obj, const string & _file, int _line) : mObj(_obj), mFile(_file), mLine(_line) 
    { 
     cout << "Constructing from " << _file << ":" << _line << endl; 
     CReferenceManager::sObjects[make_pair(mFile, mLine)]++; 
     mObj.addRef(); 
    } 

    ~CReferenceManager() 
    { 
     cout << "Destructing object created at " << mFile << ":" << mLine << endl; 
     CReferenceManager::sObjects[make_pair(mFile, mLine)]--; 
     mObj.Release(); 
    } 

    static map<pair<string, int>, int> sObjects; 
    string mFile; 
    int mLine; 
    T obj; 
} 

int main() 
{ 
... 
    // Cycle through sObjects before return, note any unreleased entries 
    return 0; 
} 

注意這只是僞代碼;我懷疑它編譯或開箱即用!

+0

這是我在這裏建議,昨天:http://stackoverflow.com/a/12806087/241536 –

+0

或多或少.. –

+0

如果我使用這個宏,它將打印FILE作爲RefManager,而不是test.cpp.My,意圖是打印test.cpp和它的行號,從Release中調用。 – anand

2

有一些這樣做的方法,但首先讓我問你一件事。爲什麼你想要手動管理引用並提供內存泄漏的機會?你可以很容易地使用boost::intrusive_ptr爲你完成這項工作(如果你不想提升,沒有問題,請參閱intrusive_ptr的實現,實現你自己的類或者將它複製到你自己的文件中)沒有內存泄漏來搜索它!

至於你的問題的答案,你可以有2 AddRef/Release一個用於調試版本,另一個版本,你應該添加AddRef位置的結構是怎樣的std::stackReleasestack彈出他們,並在結尾處,您看到多少女巫職位的參考資料仍保留在堆疊中!但是如果這是爲COM實現記住COM可能會多次調用AddRef,然後在以後刪除它們,因此您無法瞭解哪個AddRef沒有對應的Release

5

你不應該分配或在自己的代碼顯式釋放的引用,因此存儲,但均遞增或遞減是不會幫你在所有的源文件和行,因爲這些會(應!)始終在參考計數管理代碼內。

您沒有將源代碼包含到您的CReferenceManager類中,但根據您的描述,它是引用計數對象的包裝。它是否正確?正確執行這一CReferenceManager對象應確保:

  • 一個構造函數裸指針存儲指針,並且不更改引用計數(因爲你CReferenceCount類一個參考創建對象)
  • 引用總是遞減在析構函數
  • 參考在拷貝構造
  • 用於右側對象引用被遞增遞增,並且用於左側的對象引用在賦值運算符被遞減
  • 沒有明確的增量/減量的參考方法應該暴露
  • 操作 - >()方法應該將指針返回到對象
  • 應該有分離從擁有它CReferenceManager實例的引用計數對象沒有直接的方法。唯一的方法是通過分配一個新的引用計數對象。

而且,你想使AddRef()Release()方法在CReferenceCount類私人,使他們只能訪問通過類友誼CReferenceManager類。

如果您在CReferenceManager類中遵循上述規則,那麼可以通過確保每個人都通過在堆棧中分配的CReferenceManager包裝來訪問對象,從而避免泄漏或其他內存問題。換句話說:

要創建一個新的引用計數對象,將一個新創建的對象(帶有一個引用)傳遞給一個堆棧分配的CReferenceManager對象。例如:

CReferenceManager<Test1> testRef(new Test1()); 

爲了通過對象作爲參數傳遞給另一函數或方法,總是通過值傳遞CReferenceManager對象(未用參考,而不是由指針)。如果你這樣做,拷貝構造函數和析構函數將負責爲你維護引用計數。例如:

void someFunction(CReferenceManager<Test1> testObj) 
{ 
    // use testObj as if it was a naked pointer 
    // reference mananagement is automatically handled 
    printf("some value: %d\n", testObj->someValue()); 
} 

int main() 
{ 
    CReferenceManager<Test1> testRef(new Test1()); 
    someFunction(testRef); 
} 

如果需要粘在容器中的引用計數對象,然後插入通過值的CReferenceManager包裝(未它的指針,而不是對象的裸指針)。例如:

std::vector< CReferenceManager<Test1> > myVector; 
CReferenceManager<Test1> testRef(new Test1()); 
myVector.push_back(testRef); 
myVector[0]->some_method(); // invoke the object as if it was a pointer! 

我相信,如果您嚴格遵循上述規則,您會發現唯一的問題是引用計數實現中的錯誤。

遵循這些規則的示例實現在this page,儘管該解決方案缺少對多線程保護的任何支持。

我希望這有助於!

1

引用計數的原理是當用戶鏈接到對象時增加計數器並在斷開鏈接時減少計數器。

所以,你必須:

  • 操縱智能指針,不是指針,使增大/減小透明
  • 超載拷貝構造函數和分配smart_pointer的操作

符號爲例:

  • A a = new A(); refcount = 0,沒有人使用它
  • Link<A> lnk(a);的refcount = 1個
  • obj.f(lnk); OBJ存儲LNK,引用次數= 2
  • 這種方法可能由於所有權返回已被轉移到obj

所以,看看參數傳遞(可以做自動複製)並複製到異物中。

好的教程存在於CORBA星雲中。

您可能也會看到ACEICE0MQ

2

對於我參與的項目,我有類似的需求。我們有我們自己的智能指針模板類,並且由於循環引用不時出現內存泄漏。

要知道引用泄漏對象的哪個智能指針仍然活着(2個或更多),我們使用特殊的預處理器定義編譯源,以便在智能指針實現中啓用特殊的調試代碼。你可以看看我們的smart-pointer class

實質上,每個智能指針和引用計數對象都有一個唯一的ID。當我們獲取泄漏對象的id(通常使用valgrind來標識泄漏對象的內存分配的源位置)時,我們使用我們特殊的調試代碼來獲取引用對象的所有智能指針ID。然後我們使用配置文件寫下智能指針ID,在下次啓動應用程序時,我們的調試工具讀取該文件,然後知道哪個新創建的智能指針實例會觸發輸入調試器。這揭示了創建智能指針實例的堆棧跟蹤。

不可否認,這涉及到一些工作,可能只能爲大型項目帶來回報。

另一種可能性是在運行時在您的AddRef方法中記錄堆棧跟蹤。看看我的ctkBackTrace類在運行時創建堆棧跟蹤。用標準的STL類型替換Qt特定的類型應該很容易。做你問什麼

1

的方法之一,是通過AddRef和使用類似本次發佈這樣的信息:

void CReferenceCount::AddRef(const char *file=0, int line=-1) { if (file) cout << "FILE:" << file; if (line>0) count << " LINE: " << line; .... do the rest here ... } 

然後當你調用該函數,你可以使用宏類似於羅利以上建議,像這樣:

#define ADDREF(x) x.AddRef(__FILE__, __LINE__) 

這將傳遞調用的文件和行,我相信這是你所要求的。您可以控制您想要對方法中的信息執行的操作。如上所述,將它們打印出來只是一個例子。您可能希望收集更多信息,並將其記錄到另一個對象中,以便您可以記錄通話記錄,將它們寫入日誌文件等。您還可以從通話點傳遞更多信息,而不僅僅是文件和根據你需要的跟蹤類型和級別。默認參數還允許你在不傳遞任何東西的情況下使用它們(通過簡單的宏重定義),只是爲了看看最終版本的行爲如何,以及兩次堆棧推進和兩個條件檢查的開銷。

+0

贊同在Rollie的回答中指出的,因爲OP需要將宏調用放入他的CReferenceManager模板類中,所以這將不起作用。所以宏將始終打印RefManager作爲文件名。 – Sascha

+0

嗯,沒有。如果宏在頭文件中,它就會工作,並且它在其他幾個cpp文件中使用。 – DNT

+0

這裏的空間不足以粘貼示例運行,但如果有人感興趣,我可以添加一個答案。 – DNT

1

簡短的回答:你應該使用他人發佈的,即利用ADD/RELEASE宏並通過預定義的_ _ FILE _ _和_ _ LINE _ _宏,編譯器提供給您的跟蹤類的想法。

稍微長一些的答案:您還可以使用允許您遍歷堆棧並查看誰稱爲函數的功能,這比使用宏更靈活,更乾淨,但幾乎肯定會比較慢。

本頁向您展示如何在使用GCC時實現此目的:http://tombarta.wordpress.com/2008/08/01/c-stack-traces-with-gcc/

在Windows中,您可以使用一些編譯器內在函數以及符號查找功能。有關詳細信息,請查看:http://www.codeproject.com/tools/minidump.asp

請注意,在這兩種情況下,您的程序都需要至少包含一些符號才能起作用。

除非您在運行時對此有特殊要求,否則我建議您查看簡短的答案。