25 Stimmen

Speicher-/Heap-Verwaltung über DLLs hinweg

Obwohl es sich offenbar um ein sehr häufiges Problem handelt, habe ich nicht viele Informationen gefunden: Wie kann ich eine sichere Schnittstelle zwischen DLL-Grenzen in Bezug auf die Speicherzuweisung schaffen?

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. Da aber Interaktionen wie die oben beschriebene nicht ungewöhnlich sind, muss es eine Möglichkeit geben, eine sichere Speicherzuweisung zu gewährleisten.

Natürlich könnte man eine

// in DLL a
DLLEXPORT void deleteObject(MyObject* o) { delete o; }

aber vielleicht gibt es bessere Möglichkeiten (z.B. smart_ptr?). Ich las über die Verwendung von benutzerdefinierten Allokatoren beim Umgang mit STL-Containern als gut.

Meine Anfrage bezieht sich also eher auf allgemeine Hinweise auf Artikel und/oder Literatur, die sich mit diesem Thema befassen . Gibt es spezielle Fehler, auf die man achten muss (Ausnahmebehandlung?), und ist dieses Problem nur auf DLLs beschränkt oder sind auch gemeinsam genutzte UNIX-Objekte "betroffen"?

15voto

Björn Pollex Punkte 72424

Wie Sie vorgeschlagen haben, können Sie eine boost::shared_ptr um dieses Problem zu lösen. Im Konstruktor können Sie eine benutzerdefinierte Aufräumfunktion übergeben, die die deleteObject-Methode der Dll sein könnte, die den Zeiger erstellt hat. Beispiel:

boost::shared_ptr< MyObject > Instance( getObject( ), deleteObject );

Wenn Sie keine C-Schnittstelle für Ihre dll benötigen, können Sie getObject einen shared_ptr zurückgeben.

10voto

Alexander Gessler Punkte 44223

Überlastung operator new , operator delete et. al für alle Ihre DLL-Klassen und implementieren Sie sie innerhalb der DLL:

 void* MyClass::operator new(size_t numb) {
    return ::operator new(num_bytes);
 }

 void MyClass::operator delete(void* p) {
    ::operator delete(p);
 }
 ...

Dies kann leicht in einer gemeinsamen Basisklasse für alle von der DLL exportierten Klassen untergebracht werden.

Auf diese Weise werden Zuweisung und Freigabe vollständig auf dem DLL-Heap durchgeführt. Ehrlich gesagt, ich bin mir nicht sicher, ob es irgendwelche ernsthaften Fallstricke oder Portabilitätsprobleme hat - aber es funktioniert für mich.

5voto

MSalters Punkte 166675

Sie können behaupten, dass dies "sicherlich zu Abstürzen führen könnte". Komisch - "könnte" bedeutet das genaue Gegenteil von "sicher".

Nun ist die Aussage ohnehin größtenteils historisch. Es gibt eine sehr einfache Lösung: Verwenden Sie 1 Compiler, 1 Compiler-Einstellung, und linken Sie gegen die DLL-Form des CRT. (Und Sie können wahrscheinlich weg zu überspringen die letztere)

Es gibt keine spezifischen Artikel, auf die man verweisen könnte, da dies heutzutage kein Problem mehr ist. Man bräuchte sowieso die Regel 1 Compiler, 1 Einstellung. Einfache Dinge wie sizeof(std::string) davon abhängen und es sonst zu massiven ODR-Verletzungen kommen würde.

3voto

dirkgently Punkte 104289

3voto

Martin Ba Punkte 35131

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.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X