Хорошо известно, что
// in DLL a
DLLEXPORT MyObject* getObject() { return new MyObject(); }
// in DLL b
MyObject *o = getObject();
delete o;
может привести к сбоям.
Имеет или нет вышеуказанное четкую характеристику, зависит от того, как определен тип MyObject
.
Если в классе есть виртуальный деструктор (и этот деструктор не определен внутри строки), он не потерпит крах и будет демонстрировать четко определенное поведение.
Причина, по которой обычно происходит сбой, заключается в том, что delete
делает две вещи:
- вызов деструктора
- свободной памяти (по телефону
operator delete(void* ...)
)
Для класса с не виртуальным деструктором, он может делать эти вещи «встроенными», что приводит к ситуации, когда delete
внутри DLL «b» может попытаться освободить память от кучи «a» == сбой .
Однако, если деструктор MyObject
равен virtual
, то перед вызовом «свободной» функции компилятор должен определить фактический класс времени выполнения указателя , прежде чем он сможет передать правильный указатель на operator delete()
:
C ++ требует, чтобы вы передавали один и тот же адрес оператору
удалить как оператор new. Когда вы выделяете объект
используя new, компилятор неявно знает конкретный тип
объект (это то, что компилятор использует для передачи в правильной памяти
например, размер для оператора new.)
Однако, если у вашего класса есть база
класс с виртуальным деструктором, и ваш объект удаляется через
указатель на базовый класс, компилятор не знает конкретный тип
на сайте вызова, и, следовательно, не может вычислить правильный адрес
перейти к оператору delete (). Почему, спросите вы? Потому что в присутствии
множественное наследование, адрес указателя базового класса может быть
отличается от адреса объекта в памяти.
Итак, что происходит в этом
Дело в том, что при удалении объекта, который имеет виртуальный деструктор,
компилятор вызывает то, что называется деструктором удаления
вместо обычной последовательности
вызов нормального деструктора с последующим оператором delete () для восстановления
память.
Поскольку деструктор удаления является виртуальной функцией, в
во время выполнения будет вызвана реализация конкретного типа, и
эта реализация способна вычислить правильный адрес для
объект в памяти. То, что делает эта реализация, называется
обычный деструктор, вычислить правильный адрес объекта и
затем вызовите оператор delete () по этому адресу.
Похоже, что и GCC (из связанной статьи), и MSVC достигают этого, вызывая как функцию dtor, так и функцию "free" из контекста "удаления деструктора". И этот помощник по необходимости живет внутри вашей DLL и всегда будет использовать правильную кучу , даже если "a" и "b" имеют разные значения.