2013-07-08 14 views
6

Wpadłem dziś na uszkodzenie sterty spowodowane różnymi ustawieniami CRT (MTd MDd) w mojej bibliotece dll i moim rzeczywistym projekcie. To, co znalazłem, jest dziwne, że aplikacja uległa awarii, gdy ustawiam destruktor w bibliotece dll tak, aby była wirtualna. Czy jest to łatwe wytłumaczenie? Rozumiem, że nie mogę zwolnić pamięci, która nie jest na mojej stercie, ale gdzie dokładnie jest różnica, kiedy definiuję destruktor jako nie-wirtualny.CRT wirtualny destruktor

jakiś kod, żeby zrobić to trochę jaśniej

DLL

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

A mój projekt

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

Czy masz ten sam problem, przenosząc declspec do * class * ("class _declspec (dllexport) CTestClass {...}") i usuwając declspecs dla poszczególnych członków? Po prostu ciekawy. I uwaga, kod wywołujący i biblioteka DLL powinny używać tego samego CRT (debugowania lub wydania), więc to coś do rozważenia. Nie jestem nawet pewien, czy obsługiwane są tryby mieszane (nie sądzę, że tak). – WhozCraig

+6

Masz wiele kopii CRT w swoim procesie. Eksportujesz tylko metody klasy, a nie v-table. Próba wyjaśnienia, jak wszystkie te interakcje mają na celu zbombardowanie kodu, nie jest tak produktywna, jak się spodziewano.Eksportowanie klasy za pomocą metod wirtualnych wymaga wyeksportowania całej klasy, wstawienia __declspec (dllexport) obok słowa kluczowego * class *. I musisz upewnić się, że pojedynczy alokator jest używany do tworzenia i niszczenia obiektu. Bardzo trudne do zagwarantowania, chyba że kompilujesz z/MD konsekwentnie i używasz tej samej wersji kompilatora. Ujawnianie klas C++ poza granice modułów jest po prostu ryzykowne. –

+0

Masz prawdopodobnie rację, nawet jeśli zrozumiem, dlaczego to nie działa, nie pomoże mi to zbytnio. Mimo to dziękuję za twoje myśli :) – Poisonbox

Odpowiedz

2

Jest różnica między

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

i

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

W pierwszym przypadku umocowanego kompilator eksportować tylko dwie funkcje użytkownika: CTestClass :: CTestClass() i CTestClass :: ~ CTestClass(). Ale w tym drugim przypadku poleciłbyś kompilatorowi wyeksportować tabelę funkcji wirtualnych. Ta tabela jest wymagana po uzyskaniu wirtualnego destruktora. Może to być przyczyną katastrofy. Kiedy twój program próbuje wywołać destruktor wirtualny, szuka go w powiązanej tablicy funkcji wirtualnych, ale nie jest poprawnie zainicjalizowany, więc nie wiemy, gdzie to naprawdę wskazuje. Jeśli twój destruktor nie jest wirtualny, to nie potrzebujesz żadnej wirtualnej tablicy funkcji i wszystko działa dobrze.

0

Ty naprawdę nie wystarczy po kod, aby mieć pewność. Ale twój przykład nie powinien upaść, bo nie ma nic złego w tym:

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

Podejrzewam, że w rzeczywistym kodzie musi być za pomocą operatora delete na obiekt, który znajduje się operator new został stracony w kontekście dll . Bez wirtualnego słowa kluczowego prawdopodobnie przegapisz wywołanie destruktora, które wykonuje usuwanie kontekstu.

+0

"Twój przykład NIE powinien się zawiesić, ponieważ nie ma w nim nic złego", to jest dokładnie to, co myślałem również, jednak złamałem kod do tego, co zostało napisane powyżej i faktycznie zawieść. – Poisonbox

+0

Czy możesz przesłać przykładowy projekt? Musi być coś jeszcze nie tak – paulm

0

wirtualny destruktor jest potrzebny tylko wtedy, gdy masz drzewo hierarchii dziedziczenia. Wirtualne słowo kluczowe sprawi, że wskaźnik do faktycznego obiektu (a nie typu obiektu) zostanie zniszczony przez znalezienie jego destruktora w Vtable. Ponieważ w tym przykładzie przechodzenie przez podany kod, CTestClass nie dziedziczy z żadnej innej klasy, jest to w pewnym sensie klasa bazowa, a więc nie potrzebuje wirtualnego destruktora. Zakładam, że może to być inna zasada implementacji tego mechanizmu, ale nie powinieneś używać wirtualnego z klasami podstawowymi. Za każdym razem, gdy tworzysz obiekt wyprowadzony, tworzysz także jego bazę (z przyczyn polimorficznych), a baza zawsze ulega zniszczeniu (wyprowadzone tylko zostaje zniszczone, jeśli zrobisz dla niego destruktor wirtualny, a więc umieszczając go w tabeli wirtualnego podglądu środowiska wykonawczego) .

Dzięki

Powiązane problemy