72 votes

Trouver C++ statique de l'ordre d'initialisation des problèmes

Nous avons rencontrer quelques problèmes avec la statique de l'ordre d'initialisation fiasco, et je suis à la recherche de façons de passer au peigne fin tout un tas de code pour trouver les occurrences possibles. Toutes les suggestions sur la façon de le faire efficacement?

Edit: je suis obtenir de bonnes réponses sur la façon de RÉSOUDRE la statique de l'ordre d'initialisation de problème, mais ce n'est pas vraiment ma question. Je voudrais savoir comment faire pour TROUVER des objets qui sont soumis à ce problème. Evan réponse semble être le meilleur jusqu'à présent à cet égard; je ne pense pas que nous pouvons utiliser valgrind, mais nous pouvons avoir de la mémoire des outils d'analyse qui pourrait effectuer une fonction similaire. Qui permettraient de capter que les problèmes où l'ordre d'initialisation est faux, pour une de construire, et de l'ordre peut changer à chaque build. Peut-être il y a un outil d'analyse statique qui permettraient de capter cette. Notre plate-forme IBM XLC/compilateur C++ en cours d'exécution sur AIX.

84voto

Loki Astari Points 116129

Résolution de l'ordre de l'initialisation:

Tout d'abord, c'est juste un travail autour de parce que vous avez des variables globales, que vous essayez de vous débarrasser de, mais n'ont tout simplement pas eu le temps encore (vous allez à se débarrasser d'eux finalement arn-vous pas :-)

class A
{
    public:
        // Get the globale instance abc
        //
        static A& getInstance_abc()  // return reference.
        {
            static A instance_abc;
            return instance_abc;
        }
};

Cela permettra de garantir qu'il est initialisé lors de la première utilisation et détruit lorsque l'application se termine.

Multi Threaded Problème.

C++11 ne pouvons garantir que c'est thread-safe. mais le C++03 n'a pas officiellement garantir que la construction de la fonction statique des objets est thread-safe. Donc, techniquement, le getInstance_XXX() la méthode doit être protégée avec une section critique. Sur le côté positif de gcc a explicitement patch dans le cadre du compilateur qui garantit que chaque fonction statique de l'objet ne seront initialisés une fois, même en présence de threads.

Veuillez noter:
NE PAS utiliser la double vérification de verrouillage modèle pour essayer d'optimiser loin le verrouillage. Cela ne fonctionnera pas en C++

Problèmes De Création:

Lors de la création il n'y a pas de problèmes parce que nous garantissons qu'il est créé avant de pouvoir être utilisé.

Problèmes De Destruction:

Il y a un potentiel problème de l'accès à l'objet après qu'il a été détruit. Cela se produit uniquement si vous avez accès à l'objet de l'destructeur d'une autre variable globale (global je suis de référence pour les non locaux variable statique).

Solution assurez-vous de la force de l'ordre de destruction.
Rappelez-vous l'ordre de destruction est l'exact inverse de l'ordre de la construction. Donc, si vous accéder à l'objet de votre destructeur, vous devez garantir que l'objet n'a pas été détruit. Pour ce faire, vous devez garantir que l'objet est entièrement construit en avant de l'objet appelant est construit.

class B
{
    public:
        static B& getInstance_Bglob;
        {
            static B instance_Bglob;
            return instance_Bglob;;
        }

        ~B()
        {
             A::getInstance_abc().doSomthing();
             // The object abc is accessed from the destructor.
             // Potential problem.
             // You must guarantee that abc is destroyed after this object.
             // To guarantee this you must make sure it is constructed first.
             // To do this just access the object from the constructor.
        }

        B()
        {
            A::getInstance_abc();
            // abc is now fully constructed.
            // This means it was constructed before this object.
            // This means it will be destroyed after this object.
            // This means it is safe to use from the destructor.
        }
};

32voto

Warren Stevens Points 170

Je viens d'écrire un peu de code pour traquer ce problème. Nous avons une bonne taille de la base de code (1000+ fichiers) qui marchait très bien sur Windows/VC++ 2005, mais plantage au démarrage sur Solaris/gcc. J'ai écrit ce qui suit .h fichier:

#ifndef FIASCO_H
#define FIASCO_H

/////////////////////////////////////////////////////////////////////////////////////////////////////
// [WS 2010-07-30] Detect the infamous "Static initialization order fiasco"
// email warrenstevens --> [initials]@[firstnamelastname].com 
// read --> http://www.parashift.com/c++-faq-lite/ctors.html#faq-10.12 if you haven't suffered
// To enable this feature --> define E-N-A-B-L-E-_-F-I-A-S-C-O-_-F-I-N-D-E-R, rebuild, and run
//#define ENABLE_FIASCO_FINDER
/////////////////////////////////////////////////////////////////////////////////////////////////////

#ifdef ENABLE_FIASCO_FINDER

#include <iostream>
#include <fstream>

inline bool WriteFiasco(const std::string& fileName)
{
    static int counter = 0;
    ++counter;

    std::ofstream file;
    file.open("FiascoFinder.txt", std::ios::out | std::ios::app);
    file << "Starting to initialize file - number: [" << counter << "] filename: [" << fileName.c_str() << "]" << std::endl;
    file.flush();
    file.close();
    return true;
}

// [WS 2010-07-30] If you get a name collision on the following line, your usage is likely incorrect
#define FIASCO_FINDER static const bool g_psuedoUniqueName = WriteFiasco(__FILE__);

#else // ENABLE_FIASCO_FINDER
// do nothing
#define FIASCO_FINDER

#endif // ENABLE_FIASCO_FINDER

#endif //FIASCO_H

et à l'intérieur de chaque .fichier cpp dans la solution, j'ai ajouté ceci:

#include "PreCompiledHeader.h" // (which #include's the above file)
FIASCO_FINDER
#include "RegularIncludeOne.h"
#include "RegularIncludeTwo.h"

Lorsque vous exécutez votre application, vous allez obtenir un fichier de sortie comme suit:

Starting to initialize file - number: [1] filename: [p:\\OneFile.cpp]
Starting to initialize file - number: [2] filename: [p:\\SecondFile.cpp]
Starting to initialize file - number: [3] filename: [p:\\ThirdFile.cpp]

Si vous rencontrez un incident, le coupable doit être dans les derniers .rpc fichier listés. Et, à tout le moins, cela vous donnera un bon endroit pour définir des points d'arrêt, que ce code doit être l' absolu d'abord de votre code à exécuter (après quoi, vous pouvez prendre l'aide de votre code et de voir toutes les variables globales qui sont en cours d'initialisation).

Notes:

  • Il est important que vous mettez le "FIASCO_FINDER" macro aussi près du haut de votre fichier que possible. Si vous vous mettez en dessous de certains autres #inclut vous courez le risque de s'écraser avant d'identifier le fichier qui vous êtes.

  • Si vous utilisez Visual Studio, et les en-têtes précompilés, l'ajout de cette extra macro ligne pour tous vos .fichiers cpp peut être fait rapidement à l'aide de la Trouver et de dialogue remplacer pour remplacer votre #include "precompiledheader.h" avec le même texte en plus de la FIASCO_FINDER ligne (si vous cochez "expressions régulières, vous pouvez utiliser "\n" pour insérer multi-ligne de texte de remplacement)

15voto

paxdiablo Points 341644

En fonction de votre compilateur, vous pouvez placer un point d'arrêt au constructeur code d'initialisation. Dans Visual C++, c'est l' _initterm fonction qui est donnée de début et de fin de pointeur qui est une liste de la fonction à appeler.

Puis étape dans chaque fonction pour obtenir le fichier et le nom de la fonction (en supposant que vous avez compilé avec les informations de débogage). Une fois que vous avez les noms, les sortir de la fonction (retour jusqu'à _initterm) et continuer jusqu' _initterm des sorties.

Qui vous donne TOUS les il les initialiseurs statiques, et pas seulement celles qui sont dans votre code, c'est la façon la plus simple d'obtenir une liste exhaustive. Vous pouvez filtrer ceux que vous n'avez aucun contrôle sur (tels que ceux dans les bibliothèques de tiers).

La théorie est pour les autres compilateurs, mais le nom de la fonction et la capacité de le débogueur peut changer.

5voto

Ben Murrell Points 51

Il est le code que l'essentiel "initialise" C++ qui est généré par le compilateur. Un moyen facile de trouver le code / la pile d'appel à l'époque est de créer un objet statique avec quelque chose qui déréférence NULL dans le constructeur de saut dans le débogueur et explorer un peu. Le MSVC compilateur met en place un tableau de pointeurs de fonction qui est itéré pour l'initialisation statique. Vous devriez être en mesure d'accéder à cette table et déterminer tous initialisation statique qui se déroule dans votre programme.

4voto

Adisak Points 3328

Nous avons rencontrer quelques problèmes avec le initialisation statique afin fiasco, et je suis à la recherche de façons de peigne à travers un tas de code à trouver occurrences possibles. Toutes les suggestions sur la façon de le faire efficacement?

Ce n'est pas un problème trivial, mais au moins il peut le faire en suivant assez simples étapes si vous avez un facile à analyser intermédiaire-format de représentation de votre code.

1) Trouver toutes les variables globales qui ont non négligeables pour les constructeurs et les mettre dans une liste.

2) Pour chacune de ces non-trivialement-objets construits, générer l'intégralité du potentiel de la fonction de l'arbre appelé par leurs constructeurs.

3) Marcher à travers la non-trivialement-fonction constructeur d'arbre, et si le code fait référence à tout autre non-trivialement construit globals (qui sont assez facilement dans la liste que vous avez généré à la première étape), vous avez un potentiel début de l'-statique-initialisation de l'ordre d'émission.

4) Répétez les étapes 2 et 3 jusqu'à ce que vous avez épuisé la liste générée à l'étape 1.

Remarque: vous pouvez être en mesure d'optimiser cela en visitant le potentiel de la fonction de l'arbre une fois par objet de la classe plutôt qu'une fois par instance globale si vous avez plusieurs globals d'une même classe.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X