2013-07-08 13 views
6

Oggi mi sono imbattuto in un danneggiamento dell'heap causato da diverse impostazioni CRT (MTd MDd) nella mia DLL e nel mio progetto attuale. Quello che ho trovato strano è che l'applicazione si è bloccata solo quando ho impostato il distruttore nella dll come virtuale. C'è una spiegazione facile per quello? Capisco che non posso liberare memoria che non è nel mio heap, ma dove esattamente è la differenza quando definisco il distruttore come non virtuale.Distruttore virtuale CRT

Alcuni codice solo per renderlo un po 'più chiaro

La DLL

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

e il mio progetto

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

Inoltre, hai lo stesso problema spostando declspec in * class * ('classe _declspec (dllexport) CTestClass {...}') e rimuovi i declspec per membro? Solo curioso. E nota, il codice chiamante e la DLL dovrebbero usare lo stesso CRT (debug o release), quindi questo è qualcosa da considerare. Non sono nemmeno sicuro che le modalità miste siano supportate (non penso che lo siano). – WhozCraig

+6

Nel tuo processo hai più copie del CRT. Ed esporti solo i metodi di classe, non la v-table. Cercare di capire come tutto questo interagisce per bombardare il tuo codice non è così produttivo, è previsto.Esportare una classe con metodi virtuali richiede di esportare l'intera classe, mettere __declspec (dllexport) accanto alla parola chiave * class *. E devi assicurarti che un singolo allocatore sia usato per creare e distruggere l'oggetto. Molto difficile da garantire se non si costruisce con/MD in modo coerente e si utilizza la stessa esatta versione del compilatore. Esporre le classi C++ oltre i limiti del modulo è solo rischioso. –

+0

Hai ragione, anche se capisco perché non funziona, non mi aiuterà troppo. Grazie comunque per i tuoi pensieri :) – Poisonbox

risposta

2

C'è una differenza tra

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

e

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

Nel primo caso si indicato un compilatore di esportare solo due funzioni membro: CTestClass :: CTestClass() e CTestClass :: ~ CTestClass(). Ma in quest'ultimo caso istruirai un compilatore ad esportare anche la tabella delle funzioni virtuali. Questa tabella è necessaria una volta ottenuto un distruttore virtuale. Quindi potrebbe essere la causa dello schianto. Quando il tuo programma tenta di chiamare il distruttore virtuale, lo cerca nella tabella delle funzioni virtuali associate, ma non è inizializzato correttamente, quindi non sappiamo dove punta realmente. Se il tuo distruttore non è virtuale, non hai bisogno di alcuna tabella delle funzioni virtuali e tutto funziona perfettamente.

0

Non hai veramente postale sufficiente il codice per essere sicuri. Ma il tuo esempio, non dovrebbe in crash, perché non c'è niente di sbagliato con esso:

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; 
} 

Ho il sospetto che nel codice vero e proprio è necessario utilizzare operatore delete su un oggetto che è operatore di nuovo è stato eseguito nel contesto del dll . E senza la parola chiave virtuale è probabile che manchi la chiamata al distruttore che sta eseguendo il cross context delete.

+0

"il tuo esempio NON deve andare in crash perché non c'è niente di sbagliato in esso", questo è esattamente quello che pensavo anch'io, tuttavia ho infranto il codice fino a ciò che è stato scritto sopra e lo fa in effetti fallire. – Poisonbox

+0

Puoi caricare un progetto di esempio? Ci deve essere qualcos'altro che non va – paulm

0

distruttore virtuale è necessario solo quando si dispone di un albero di gerarchia di ereditarietà. La parola chiave virtuale si assicura che quel puntatore all'oggetto reale (non al tipo dell'oggetto) venga distrutto trovando il suo distruttore nel Vtable. Dato che in questo esempio, passando per il codice che hai dato, CTestClass non eredita da nessuna altra classe, è in un modo una classe base e quindi non ha bisogno di un distruttore virtuale. Presumo che forse ci sia un'altra regola di implementazione sotto la cappa che causa questo, ma non dovresti usare virtual con le classi base. Ogni volta che si crea un oggetto derivato, si crea anche la sua base (per motivi polimorfi) e la base viene sempre distrutta (il derivato viene distrutto solo se si crea il distruttore per esso virtuale, quindi lo si colloca in una tabella di vlookup (virtuale) di runtime) .

Grazie

Problemi correlati