2013-07-08 5 views
6

Я столкнулся с коррупцией кучи сегодня, вызванной различными настройками ЭЛТ (MTd MDd) в моей DLL и моим фактическим проектом. Что я нашел странным, так это то, что приложение только разбилось, когда я установил деструктор в 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 _declspec (dllexport) CTestClass {...}') и удаляйте pers-member declspecs? Просто любопытно. И обратите внимание, что вызывающий код и DLL должны использовать один и тот же CRT (отладка или выпуск), поэтому нужно что-то рассмотреть. Я даже не уверен, что поддерживаются смешанные режимы (я не думаю, что это так). – WhozCraig

+6

У вас есть несколько копий ЭЛТ в вашем процессе. И вы экспортируете только методы класса, а не 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 , И без ключевого слова virtual вы, вероятно, пропустите вызов деструктора, который выполняет перекрестный контекст delete.

+0

«ваш пример не должен ломаться, потому что в нем нет ничего плохого», это именно то, что я думал тоже, однако я сломал код до того, что было написано выше, и оно действительно потерпеть неудачу. – Poisonbox

+0

Можете ли вы загрузить образец проекта? Должно быть что-то еще не так – paulm

0

Виртуальный деструктор необходим только тогда, когда у вас есть дерево иерархии наследования. Виртуальное ключевое слово будет гарантировать, что указатель на фактический объект (а не тип объекта) будет уничтожен путем поиска его деструктора в Vtable. Поскольку в этом примере, перейдя по коду, который вы указали, CTestClass не наследуется от какого-либо другого класса, он в некотором роде является базовым классом и, следовательно, не нуждается в виртуальном деструкторе. Я предполагаю, что, возможно, другой из правил реализации капота вызывает это, но вы не должны использовать виртуальные с базовыми классами. Каждый раз, когда вы создаете производный объект, вы также создаете его базу (для полиморфного разума), и база всегда уничтожается (производная только уничтожается, если вы создаете для нее деструктор виртуальный, следовательно, помещая ее в таблицу vlookup (virtual) ,

Благодаря