Es ist allgemein bekannt, dass
// in DLL a
DLLEXPORT MyObject* getObject() { return new MyObject(); }
// in DLL b
MyObject *o = getObject();
delete o;
kann durchaus zu Abstürzen führen.
Ob das oben genannte ein wohldefiniertes Merkmal ist, hängt davon ab, wie die MyObject
Typ definiert ist.
Wenn die Klasse einen virtuellen Destruktor hat (und dieser Destruktor nicht inline definiert ist), dann wird sie nicht abstürzen und ein wohldefiniertes Verhalten zeigen.
Als Grund für diesen Absturz wird in der Regel angeführt, dass delete
tut zwei Dinge:
- Destruktor aufrufen
- Speicher freigeben (durch Aufruf von
operator delete(void* ...)
)
Bei einer Klasse mit einem nicht-virtuellen Destruktor kann sie diese Dinge "inline" tun, was dazu führt, dass delete
innerhalb der DLL "b" kann versuchen, Speicher aus dem Heap "a" freizugeben == Absturz.
Wenn jedoch der Destruktor von MyObject
est virtual
dann wird der Compiler vor dem Aufruf der "free"-Funktion muss die tatsächliche Laufzeitklasse des Zeigers bestimmen bevor es den richtigen Zeiger an operator delete()
:
C++ schreibt vor, dass Sie genau dieselbe Adresse an den Operator delete genau dieselbe Adresse übergeben, die der Operator new zurückgibt. Bei der Zuweisung eines Objekts mit new zuweisen, kennt der Compiler implizit den konkreten Typ des Objekts (was der Compiler verwendet, um die richtige Speichergröße an den Operator new Größe an den Operator new zu übergeben).
Wenn Ihre Klasse jedoch eine Basis Klasse mit einem virtuellen Destruktor hat, und Ihr Objekt durch einen Zeiger auf die Basisklasse gelöscht wird, kennt der Compiler nicht den konkreten Typ an der Aufrufstelle und kann daher nicht die korrekte Adresse berechnen, die an den an den Operator delete() zu übergeben. Warum, werden Sie sich fragen? Weil bei Vorhandensein von Mehrfachvererbung die Adresse des Zeigers der Basisklasse anders sein kann anders sein kann als die Adresse des Objekts im Speicher.
Was passiert also in diesem Fall, dass Sie ein Objekt löschen, das einen virtuellen Destruktor hat, ruft der Compiler einen so genannten löschenden Destruktor anstelle der üblichen Sequenz eines Aufruf des normalen Destruktors, gefolgt vom Operator delete(), um den den Speicher zurückzugewinnen.
Da der löschende Destruktor eine virtuelle Funktion ist, wird zur Laufzeit die Implementierung des konkreten Typs aufgerufen, und diese Implementierung ist in der Lage, die korrekte Adresse für das Objekt des Objekts im Speicher zu berechnen. Diese Implementierung ruft dann den regulären Destruktor auf, berechnet die korrekte Adresse des Objekts und ruft dann den Operator delete() für diese Adresse auf.
Es scheint, dass sowohl GCC (aus dem verlinkten Artikel) als auch MSVC dies erreichen, indem sie sowohl den dtor als auch die "free"-Funktion aus dem Kontext eines "deleting destructor" aufrufen. Und dieser Helfer lebt notwendigerweise innerhalb Ihrer DLL und wird immer den richtigen Heap verwenden , auch wenn "a" und "b" einen anderen haben.