2009-12-23 7 views
6

J'ai récemment transféré mon application de VC++ 7 à VC++ 9. Maintenant, il se bloque parfois à la sortie - l'exécution commence à appeler des destructeurs d'objets globaux et une violation d'accès se produit dans l'un d'entre eux.Que signifie "dynamique" dans "dynamique atexit destructor"?

Chaque fois que je observateur l'appel empile les fonctions sont top:

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

La question est quel est le sens du mot dans « destructor atexit dynamique » « dynamique »? Peut-il me fournir des informations supplémentaires?

Répondre

7

il est difficile de cerner exactement le problème sans le code réel, mais peut-être vous pouvez trouver vous-même après avoir lu ceci:

de http://www.gershnik.com/tips/cpp.asp (le lien est mort maintenant voir ci-dessous)

atexit() et bibliothèques dynamiques/partagées

Les bibliothèques standard C et C++ incluent une fonction parfois utile: atexit(). Il permet à l'appelant d'enregistrer un rappel qui va être appelé lorsque l'application se termine (normalement). En C++, il est également intégré au mécanisme qui appelle les destructeurs d'objets globaux afin que les choses créées avant un appel donné à atexit() soient détruites avant le callback et vice versa. Tout cela devrait être bien connu et cela fonctionne parfaitement bien jusqu'à ce que DLL ou bibliothèques partagées entrent dans l'image. Le problème est, bien sûr, que les bibliothèques dynamiques ont leur propre durée de vie qui, en général, pourrait se terminer avant celle de l'application principale. Si un code dans une DLL enregistre une de ses propres fonctions en tant que rappel atexit(), ce rappel devrait être mieux appelé avant que la DLL ne soit déchargée. Sinon, un crash ou quelque chose de pire se produira pendant la sortie de l'application principale. (Il est notoirement difficile de déboguer les plantages en cours de sortie, car de nombreux débogueurs ont des problèmes avec les processus de mort).

Ce problème est beaucoup mieux connu dans le contexte des destructeurs d'objets globaux C++ (qui, comme mentionné plus haut, sont les frères d'atexit()). Évidemment, toute implémentation C++ sur une plate-forme supportant les bibliothèques dynamiques devait gérer ce problème et la solution unanime consistait à appeler les destructeurs globaux soit lorsque la bibliothèque partagée était déchargée, soit à la sortie de l'application, selon la première éventualité. Jusqu'ici tout va bien, sauf que certaines implémentations "ont oublié" d'étendre le même mécanisme à l'ancienne version atexit(). Puisque la norme C++ ne dit rien sur les bibliothèques dynamiques, ces implémentations sont techniquement correctes, mais cela n'aide pas le pauvre programmeur qui, pour une raison ou une autre, doit appeler atexit() en passant un callback qui réside dans une DLL.

Sur les plates-formes, je connais la situation est la suivante. MSVC sur Windows, GCC sur Linux et Solaris et SunPro sur Solaris ont tous un «droit» atexit() qui fonctionne de la même manière que les destructeurs globaux. Cependant, GCC sur FreeBSD au moment de la rédaction de ce document a un code "cassé" qui enregistre toujours les rappels à exécuter sur l'application plutôt que la sortie de la bibliothèque partagée. Cependant, comme promis, les destructeurs globaux fonctionnent bien même sur FreeBSD.

Que devriez-vous faire en code portable? Une solution est, bien sûr, d'éviter atexit() complètement. Si vous avez besoin de sa fonctionnalité, il est facile de le remplacer par les C de Destructeurs de la manière suivante

//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(); 
... 

Cela fonctionne au détriment de beaucoup dactylographie et interface non intuitive.Il est important de noter qu'il n'y a aucune perte de fonctionnalité par rapport à la version atexit(). Le destructeur de rappel ne peut pas lancer d'exceptions, mais il en va de même pour les fonctions invoquées par atexit. La fonction callback :: register() peut ne pas être thread-safe sur une plate-forme donnée mais atexit() (la norme C++ est actuellement silencieuse sur les threads donc l'implémentation de atexit() thread-safe est en cours)

Et si vous voulez éviter toutes les dactylographies ci-dessus? Il y a généralement un moyen et il repose sur un tour simple. Au lieu d'appeler atexit() brisé, nous devons faire ce que le compilateur C++ fait pour enregistrer les destructeurs globaux. Avec GCC et d'autres compilateurs qui implémente Itanium ABI (largement utilisé pour les plates-formes non Itanium) l'incantation magique est appelée __cxa_atexit. Voici comment l'utiliser. Commencez par placer le code ci-dessous dans un en-tête d'utilitaire. Il enregistre le rappel dans une seule liste globale de la même manière que ne le fait atexit(). Cependant, il associe également les deux autres paramètres. Le deuxième paramètre est juste une chose agréable à avoir. Il permet au callback d'être passé dans un contexte (comme dans le cas de certains objets) et ainsi un seul callback peut être réutilisé pour plusieurs nettoyages. Le troisième paramètre est celui dont nous avons vraiment besoin. C'est simplement un "cookie" qui identifie la bibliothèque partagée qui devrait être associée au rappel. Lorsqu'une bibliothèque partagée est déchargée, son code de nettoyage parcourt la liste de rappel atexit et appelle (et supprime) tous les rappels qui ont un cookie correspondant à celui associé à la bibliothèque en cours de déchargement. Quelle devrait être la valeur du cookie? Ce n'est pas l'adresse de début de la DLL et pas son handle dlopen() comme on pourrait le supposer. Au lieu de cela, le descripteur est stocké dans une variable globale spéciale __dso_handle gérée par C++ runtime.

La fonction safe_atexit doit être en ligne. De cette façon, il choisit quel __dso_handle est utilisé par le module appelant, ce qui est exactement ce dont nous avons besoin.

Devriez-vous utiliser cette approche au lieu de l'approche verbeuse et plus portable ci-dessus? Probablement pas, mais qui sait quelles exigences vous pourriez avoir. Pourtant, même si vous ne l'utilisez jamais, il est utile d'être conscient de la façon dont les choses fonctionnent, c'est pourquoi il est inclus ici.

+0

Cela implique-t-il que "dynamic" provient de "dynamic load library"? – sharptooth

+0

Aucun terme ne fait référence à l'enregistrement dynamique du rappel de la fonction atexit pendant l'exécution, c'est-à-dire par opposition à l'enregistrement statique effectué au moment de la compilation. Il est utile pour les bibliothèques de chargement dynamiques puisque vous pouvez supprimer une fonction de rappel de la liste atexit si à un certain point votre application décide de décharger une DLL déjà chargée, dans ce cas vous pouvez également appeler manuellement tout code de nettoyage sans avoir besoin de relayer ateixt. – Alon

+0

lien est mort. Vous devriez envisager de copier les informations pertinentes dans votre réponse. –