2013-07-08 17 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 {...}')に移動してメンバーごとのdeclspecを削除することで同じ問題がありますか?ちょっと興味があるんだけど。そして、呼び出しコードとDLLは、同じCRT(デバッグまたはリリース)を使用している必要があるので、考慮すべき点があることに注意してください。私は混合モードがサポートされているかどうかも分かりません(私はそれがそうではないと思います)。 – WhozCraig

+6

プロセス内に複数のCRTのコピーがあります。そして、クラス・メソッドだけをエクスポートし、vテーブルはエクスポートしません。これがどのように相互作用してコードを爆破するのかを推測しようとするのは、それほど生産的ではないと予想されます。仮想メソッドを使用してクラスをエクスポートするには、クラス全体をエクスポートする必要があります。* class *キーワードの隣に__declspec(dllexport)を配置します。オブジェクトを作成して破棄するには、単一のアロケータを使用する必要があります。あなたが/ MDを一貫して構築し、まったく同じコンパイラバージョンを使用しない限り、保証するのは非常に難しいです。モジュールの境界を越えてC++クラスを公開するのは危険です。 –

+0

あなたは正しいとはいえ、それがうまくいかない理由がわかっても、あまり助けにならないでしょう。とにかくあなたの考えのためにとにかく:) – Poisonbox

答えて

2

あなたは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

仮想デストラクタは、継承階層ツリーを持つ場合にのみ必要です。仮想キーワードは、Vtable内のデストラクタを見つけることによって実際のオブジェクトへのポインタ(オブジェクトの型ではない)が破壊されることを確認します。この例では、CTestClassは与えられたコードに従っているので、他のクラスから継承していないので、基本クラスであるため、仮想デストラクタは必要ありません。私はおそらくこれを引き起こしているフードの実装ルールの下で別のものを想定していますが、基本クラスで仮想を使うべきではありません。派生オブジェクトを作成するときはいつでも、そのベースを(多態的な理由で)作成し、ベースは常に破棄されます(派生オブジェクトは仮想のデストラクタを作成してランタイムvlookup(仮想)テーブルに置くと破棄されます) 。

おかげ