2009-12-23 11 views
6

Ich habe kürzlich meine Anwendung von VC++ 7 auf VC++ 9 portiert. Jetzt stürzt es manchmal beim Beenden ab - die Laufzeit startet den Aufruf von Destruktoren für globale Objekte und eine Zugriffsverletzung tritt in einem von ihnen auf.Was bedeutet "dynamisch" in "dynamischer atexit-Destruktor"?

Immer, wenn ich Beobachter der Ruf der Top-Funktionen stapeln sind:

CMyClass::~CMyClass() <- crashes here 
dynamic atexit destructor for 'ObjectName' 
_CRT_INIT() 
some more runtime-related functions follow 

Die Frage ist, was die Bedeutung des Wortes „dynamisch“ in „dynamischen atexit destructor“? Kann es mir zusätzliche Informationen geben?

Antwort

7

sein schwierig, das genaue Problem, ohne den eigentlichen Code zu lokalisieren, aber vielleicht können Sie es selbst nach der Lektüre dieses finden:

von http://www.gershnik.com/tips/cpp.asp (Link ist jetzt tot siehe unten)

atexit() und Dynamische/gemeinsam genutzte Bibliotheken

C- und C++ - Standardbibliotheken enthalten eine manchmal nützliche Funktion: atexit(). Es ermöglicht dem Anrufer, einen Rückruf zu registrieren, der beim Beenden der Anwendung (normalerweise) aufgerufen wird. In C++ ist es auch in den Mechanismus integriert, der Destruktoren globaler Objekte aufruft, so dass Dinge, die vor einem bestimmten Aufruf von atexit() erzeugt wurden, vor dem Callback zerstört werden und umgekehrt. All dies sollte gut bekannt sein und es funktioniert einwandfrei, bis DLLs oder Shared Libraries in das Bild kommen.

Das Problem ist natürlich, dass dynamische Bibliotheken ihre eigene Lebensdauer haben, die im Allgemeinen vor der Hauptanwendung enden könnte. Wenn ein Code in einer DLL eine seiner eigenen Funktionen als atexit() - Callback registriert, sollte dieser Callback besser aufgerufen werden, bevor die DLL entladen wird. Andernfalls wird während des Hauptanwendungsexits ein Absturz oder etwas Schlimmeres auftreten. (Es ist notorisch schwer zu debuggen, wenn es zu bösen Abstürzen während des Beendens kommt, da viele Debugger Probleme mit sterbenden Prozessen haben).

Dieses Problem ist im Kontext der Destruktoren von C++ - globalen Objekten (die, wie oben erwähnt, die Brüder von atexit() sind) viel besser bekannt. Offensichtlich musste jede C++ - Implementierung auf einer Plattform, die dynamische Bibliotheken unterstützt, mit diesem Problem fertig werden. Die einhellige Lösung bestand darin, die globalen Destruktoren entweder beim Entladen der gemeinsam genutzten Bibliothek oder beim Beenden der Anwendung aufzurufen, je nachdem, welcher Fall zuerst eintritt.

So weit so gut, außer dass einige Implementierungen "vergessen", den gleichen Mechanismus auf die einfache alte atexit() zu erweitern. Da der C++ - Standard nichts über dynamische Bibliotheken sagt, sind solche Implementierungen technisch "korrekt", aber das hilft dem armen Programmierer nicht, der aus irgendeinem Grund atexit() aufrufen muss, um einen Callback zu übergeben, der sich in einer DLL befindet.

Auf den Plattformen weiß ich über die Situation ist wie folgt. MSVC unter Windows, GCC unter Linux und Solaris sowie SunPro unter Solaris haben alle eine "richtige" atexit(), die genauso funktioniert wie globale Destruktoren. Allerdings hat GCC unter FreeBSD zum Zeitpunkt dieses Schreibens eine "kaputte", die immer Callbacks registriert, die auf der Anwendung ausgeführt werden, anstatt den Ausgang der gemeinsam genutzten Bibliothek. Wie versprochen, funktionieren die globalen Destruktoren auch unter FreeBSD einwandfrei.

Was sollten Sie in tragbarem Code tun? Eine Lösung besteht natürlich darin, atexit() vollständig zu vermeiden. Wenn Sie seine Funktionalität benötigen, ist es einfach, es mit C++ Destruktoren in der folgenden Weise auf Kosten der

//Code with atexit() 

void callback() 
{ 
    //do something 
} 

... 
atexit(callback); 
... 

//Equivalent code without atexit() 

class callback 
{ 
public: 
    ~callback() 
    { 
     //do something 
    } 

    static void register(); 
private: 
    callback() 
    {} 

    //not implemented 
    callback(const callback &); 
    void operator=(const callback &); 
}; 

void callback::register() 
{ 
    static callback the_instance; 
} 

... 
callback::register(); 
... 

Das funktioniert viel Typisierung zu ersetzen und nicht-intuitive Schnittstelle.Beachten Sie, dass die Funktionalität im Vergleich zur atexit() - Version nicht beeinträchtigt wird. Der Callback-Destruktor kann keine Exceptions auslösen, aber auch Funktionen, die von atexit aufgerufen werden. Die Funktion callback :: register() ist möglicherweise nicht threadsicher auf einer bestimmten Plattform, aber das gleiche gilt für atexit() (der C++ - Standard schweigt derzeit über Threads, sodass die Implementierung von atexit() thread-sicher ist)

Was ist, wenn Sie alle oben genannten Eingaben vermeiden möchten? Es gibt normalerweise einen Weg und es beruht auf einem einfachen Trick. Anstatt gebrochenes atexit() aufzurufen, müssen wir tun, was auch immer der C++ - Compiler tut, um globale Destruktoren zu registrieren. Mit GCC und anderen Compilern, die sogenannte Itanium ABI implementieren (weit verbreitet für Nicht-Itanium-Plattformen), heißt die Zauberformel __cxa_atexit. Hier ist, wie man es benutzt. Setzen Sie zuerst den Code unten in einige Dienstprogramm-Header

Der Weg __cxa_atexit funktioniert wie folgt. Es registriert den Rückruf in einer einzigen globalen Liste auf die gleiche Weise, wie dies bei atexit() ohne DLL der Fall ist. Es verbindet jedoch auch die anderen beiden Parameter damit. Der zweite Parameter ist einfach ein nettes Ding. Es ermöglicht dem Callback, einen Kontext zu übergeben (wie bei einigen Objekten), sodass ein einzelner Callback für mehrere Aufräumvorgänge wiederverwendet werden kann. Der dritte Parameter ist der, den wir wirklich brauchen. Es ist einfach ein "Cookie", der die gemeinsam genutzte Bibliothek identifiziert, die dem Rückruf zugeordnet werden sollte. Wenn eine gemeinsam genutzte Bibliothek entladen wird, durchläuft ihr Bereinigungscode die atexit-Rückrufliste und ruft (und entfernt) alle Rückrufe mit einem Cookie, das mit demjenigen übereinstimmt, der der gerade entladenen Bibliothek zugeordnet ist. Was sollte der Wert des Cookies sein? Es ist nicht die DLL-Startadresse und nicht seine dlopen() - Handle, wie man annehmen könnte. Stattdessen wird das Handle in einer speziellen globalen Variablen __dso_handle gespeichert, die von C++ - Laufzeit verwaltet wird.

Die Funktion safe_atexit muss inline sein. Auf diese Weise wählt es, was auch immer __dso_handle vom aufrufenden Modul verwendet wird, was genau das ist, was wir brauchen.

Sollten Sie diesen Ansatz anstelle der ausführlichen und portabler oben verwenden? Wahrscheinlich nicht, aber wer weiß, welche Anforderungen Sie haben könnten. Dennoch, auch wenn Sie es nie benutzen, hilft es, sich darüber bewusst zu sein, wie die Dinge funktionieren, deshalb ist es hier enthalten.

+0

Bedeutet dies, dass "dynamic" aus "dynamic load library" stammt? – sharptooth

+0

Nein der Begriff bezieht sich auf die dynamische Registrierung des Rückrufs der atexit-Funktion während der Laufzeit, dh im Gegensatz zur statischen Registrierung, die während der Kompilierungszeit durchgeführt wurde. Es ist nützlich für dynamische Ladebibliotheken, da Sie eine Callback-Funktion aus der atexit-Liste entfernen können, wenn Ihre Anwendung an einer beliebigen Stelle eine zuvor geladene DLL entlädt. In diesem Fall können Sie auch manuell jeden Cleanup-Code aufrufen, ohne weiterleiten zu müssen atixt. – Alon

+0

Verbindung ist tot. Sie sollten die relevanten Informationen in Ihre Antwort kopieren. –