2013-07-08 163 views
6

我遇到了由於我的dll和我的實際項目中的不同CRT設置(MTd MDd)而導致的堆腐敗。 我發現奇怪的是,當我將dll中的析構函數設置爲虛擬時,應用程序只會崩潰。 有沒有一個簡單的解釋呢?我知道我不能釋放那些不在我的堆上的內存,但是當我將析構函數定義爲非虛擬的時候,其中的差異究竟在哪裏。CRT虛擬析構函數

一些代碼只是爲了更清晰一點

的DLL

#pragma once 
class CTestClass 
{ 
public: 
    _declspec(dllexport) CTestClass() {}; 
    _declspec(dllexport) virtual ~CTestClass() {}; 
}; 

而且我的項目

int main(int argc, char* argv[]) 
{ 
    CTestClass *foo = new CTestClass; 
    delete foo; // Crashes if the destructor is virtual but works if it's not 
} 
+0

此外,你是否有同樣的問題,通過將declspec移動到* class *('class _declspec(dllexport)CTestClass {...}')並刪除每個成員declspecs?只是好奇。注意,調用代碼和DLL應該使用相同的CRT(調試或發佈),因此需要考慮。我甚至不確定混合模式是否受支持(我不認爲它是)。 – WhozCraig

+6

您的流程中有多個CRT副本。你只導出類方法,而不是v表。試圖理解這一切如何相互作用來轟炸你的代碼並不是那麼有效,這是可以預料的。使用虛擬方法導出類需要導出整個類,並將__declspec(dllexport)放在* class *關鍵字旁邊。而且您必須確保使用單個分配器來創建和銷燬該對象。很難保證,除非你始終用/ MD編譯並使用完全相同的編譯器版本。在模塊邊界上暴露C++類是有風險的。 –

+0

你是對的,即使我找出爲什麼它不起作用,它不會幫助我太多。感謝你的想法:) – Poisonbox

回答

2

class CTestClass 
{ 
public: 
    _declspec(dllexport) CTestClass() {} 
    _declspec(dllexport) virtual ~CTestClass() {} 
}; 

__declspec(dllexport) class CTestClass 
{ 
public: 
    CTestClass() {} 
    virtual ~CTestClass() {} 
}; 

之間的差異在你指示編譯器出口只有兩個成員函數在前一種情況:CTestClass :: CTestClass()和CTestClass ::〜CTestClass()。但在後一種情況下,您會指示編譯器導出虛擬函數表。一旦你有了一個虛擬析構函數,這個表就是必需的。所以這可能是事故的原因。當程序嘗試調用虛擬析構函數時,它會在關聯的虛函數表中查找它,但它沒有正確初始化,因此我們不知道它真正指向的位置。如果你的析構函數不是虛擬的,那麼你不需要任何虛函數表,並且一切正常。

0

你真的不交足夠的代碼以確保萬無一失。但是,你的例子應該不會崩潰,因爲沒有什麼錯:

int main(int argc, char* argv[]) 
{ 
    // 1. Allocated an instance of this class in *this/exe* heap, not the DLL's heap 
    // if the constructor allocates memory it will be allocated from the DLL's heap 
    CTestClass *foo = new CTestClass; 

    // 2. Call the destructor, if it calls delete on anything it will be freed from the DLL's heap since thats where the destructor is executing from. Finally we free the foo object from *this/exe* heap - no problems at all. 
    delete foo; 
} 

我懷疑,在你真正的代碼,你必須使用運營商的目標是誰的運營商新的是在DLL的上下文中執行上刪除。如果沒有虛擬關鍵字,你可能會錯過執行交叉上下文刪除的析構函數調用。

+0

「你的例子不應該崩潰,因爲它沒有任何問題」,這正是我的想法,但是我把代碼分解成了上面所寫的內容,事實上它確實如此失敗。 – Poisonbox

+0

你可以上傳一個示例項目嗎?一定有其他事情出錯了 – paulm

0

虛擬析構函數只有當你有一些繼承層次樹時才需要。 Virtual關鍵字將確保指向實際對象(而不是對象的類型)的指針通過在Vtable中查找其析構函數而被銷燬。由於在這個例子中,通過你給出的代碼,CTestClass不是從任何其他類繼承的,它以一種基類的方式,因此不需要虛擬析構函數。我假設有可能是另一個引擎蓋實現規則,但不應該在基類中使用虛擬。任何時候你創建一個派生的對象,你也創建它的基礎(爲了多態的原因)並且基礎總是被銷燬(如果你爲它創建了析構函數,派生只會被銷燬,因此將它放入運行時的虛擬表) 。

感謝