web-dev-qa-db-fra.com

Recherche des problèmes d'ordre d'initialisation statique C++

Nous avons rencontré quelques problèmes avec le fiasco d'ordre de initialisation static , et je cherche des moyens de passer au peigne fin une grande quantité de code pour rechercher les occurrences possibles. Des suggestions sur la façon de le faire efficacement?

Edit: J'obtiens de bonnes réponses sur la façon de résoudre le problème d'ordre d'initialisation statique, mais ce n'est pas vraiment ma question. J'aimerais savoir comment TROUVER les objets qui sont sujets à ce problème. La réponse d'Evan semble être la meilleure à ce jour; Je ne pense pas que nous puissions utiliser valgrind, mais nous pouvons avoir des outils d’analyse de mémoire qui pourraient remplir une fonction similaire. Cela ne réglerait les problèmes que lorsque l'ordre d'initialisation est incorrect pour une construction donnée et que cet ordre peut changer à chaque génération. Peut-être existe-t-il un outil d'analyse statique qui permettrait de résoudre ce problème. Notre plate-forme est un compilateur IBM XLC/C++ fonctionnant sous AIX.

55
Fred Larson

Ordre de résolution de l'initialisation:

Tout d’abord, c’est une solution temporaire car vous avez des variables globales que vous essayez de supprimer mais que vous n’avez pas encore eu le temps (vous allez vous en débarrasser un jour ou l’autre) :-)

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

Cela garantira qu'il est initialisé lors de la première utilisation et détruit à la fin de l'application.

Problème multi-threadé:

C++ 11 est-ce que garantit qu'il s'agit d'un thread-safe:

§6.7 [stmt.dcl] p4
Si le contrôle entre la déclaration simultanément alors que la variable est en cours d'initialisation, l'exécution simultanée doit attendre la fin de l'initialisation.

Cependant, C++ 03 ne pas garantit officiellement que la construction des objets fonction statiques est thread-safe. Donc techniquement, la méthode getInstance_XXX() doit être protégée avec une section critique. Du côté positif, gcc a un patch explicite faisant partie du compilateur, garantissant que chaque objet de fonction statique ne sera initialisé qu’une fois, même en présence de threads.

Remarque: N'utilisez pas le verrouillage vérifié pour éviter le coût du verrouillage. Cela ne fonctionnera pas en C++ 03.

Problèmes de création:

Lors de la création, il n'y a pas de problème car nous garantissons qu'il a été créé avant de pouvoir être utilisé.

Problèmes de destruction:

Il y a un problème potentiel d'accès à l'objet après sa destruction. Cela se produit uniquement si vous accédez à l'objet à partir du destructeur d'une autre variable globale (par global, je fais référence à une variable statique non locale).

La solution consiste à vous assurer que vous forcez l'ordre de destruction.
N'oubliez pas que l'ordre de destruction est l'inverse exact de l'ordre de construction. Donc, si vous accédez à l'objet dans votre destructeur, vous devez garantir que l'objet n'a pas été détruit. Pour ce faire, vous devez simplement vous assurer que l'objet est entièrement construit avant que l'objet appelant ne soit 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.
        }
};
64
Martin York

Je viens d'écrire un peu de code pour dépister ce problème. Nous avons une base de code de bonne taille (plus de 1000 fichiers) qui fonctionnait correctement sous Windows/VC++ 2005, mais qui se bloquait au démarrage sous Solaris/gcc . J'ai écrit le fichier .h suivant:

#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 dans every .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écuterez votre application, vous obtiendrez 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 problème, le coupable doit figurer dans le dernier fichier .cpp répertorié. Et à tout le moins, cela vous donnera un bon endroit pour définir des points d'arrêt, car ce code devrait être le absolu en premier de votre code à exécuter (après quoi vous pourrez parcourir votre code et voir tous les globals qui sont en cours d'initialisation).

Remarques:

  • Il est important de placer la macro "FIASCO_Finder" aussi près que possible du début de votre fichier. Si vous le mettez sous un autre #includes, vous courez le risque de plantage avant d'identifier le fichier dans lequel vous vous trouvez.

  • Si vous utilisez Visual Studio et les en-têtes pré-compilés, vous pouvez ajouter rapidement cette ligne de macro supplémentaire à tous de vos fichiers .cpp en utilisant la boîte de dialogue Rechercher/remplacer pour remplacer votre en-tête précompilé "#include" existant. .h "avec le même texte plus la ligne FIASCO_Finder (si vous cochez" expressions régulières, vous pouvez utiliser "\ n" pour insérer du texte de remplacement multiligne) 

30
Warren Stevens

En fonction de votre compilateur, vous pouvez placer un point d'arrêt au code d'initialisation du constructeur. Dans Visual C++, il s'agit de la fonction _initterm, à laquelle sont attribués les pointeurs de début et de fin de la liste des fonctions à appeler.

Puis entrez dans chaque fonction pour obtenir le nom du fichier et de la fonction (en supposant que vous ayez compilé avec les informations de débogage). Une fois que vous avez les noms, quittez la fonction (sauvegardez à _initterm) et continuez jusqu’à la sortie de _initterm.

Cela vous donneallles initialiseurs statiques, pas seulement ceux de votre code - c'est le moyen le plus simple d'obtenir une liste exhaustive. Vous pouvez filtrer ceux sur lesquels vous n'avez aucun contrôle (tels que ceux des bibliothèques tierces).

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

14
paxdiablo

Il existe un code qui "initialise" essentiellement C++ qui est généré par le compilateur. Un moyen facile de trouver ce code/la pile d'appels à ce moment-là consiste à créer un objet statique avec quelque chose qui supprime la référence NULL dans le constructeur - divise le débogueur et explore un peu. Le compilateur MSVC établit une table de pointeurs de fonction qui est itérée pour l'initialisation statique. Vous devriez pouvoir accéder à cette table et déterminer toutes les initialisations statiques ayant lieu dans votre programme.

5
Ben Murrell

peut-être utiliser valgrind pour trouver l’utilisation de la mémoire non initialisée. La meilleure solution au "fiasco d'ordre d'initialisation statique" consiste à utiliser une fonction statique qui renvoie une instance de l'objet comme celle-ci:

class A {
public:
    static X &getStatic() { static X my_static; return my_static; }
};

De cette façon, vous accéderez à votre objet statique en appelant getStatic, cela garantira qu'il sera initialisé lors de la première utilisation. 

Si vous devez vous préoccuper de l'ordre de désinitialisation, renvoyez un nouvel objet au lieu d'un objet alloué de manière statique. 

EDIT: supprimé l’objet statique redondant, je ne sais pas pourquoi, mais j’ai mélangé et assorti deux méthodes d’avoir un statique ensemble dans mon exemple original.

5
Evan Teran

Nous avons rencontré des problèmes avec le fiasco de l'ordre d'initialisation statique, et je cherche des moyens de combiner à travers beaucoup de code pour trouver occurrences possibles. Aucune suggestion sur comment faire cela efficacement?

Ce n'est pas un problème trivial, mais au moins, vous pouvez suivre des étapes assez simples si vous avez une représentation facile à analyser du format intermédiaire.

1) Trouvez tous les globals qui ont des constructeurs non triviaux et mettez-les dans une liste.

2) Pour chacun de ces objets construits de manière non triviale, générez l'intégralité de l'arborescence de fonctions potentielles appelée par leurs constructeurs.

3) Parcourez l’arborescence des fonctions de constructeur non trivial et si le code fait référence à d’autres globaux non construits de manière triviale (qui se trouvent assez facilement dans la liste que vous avez générée à la première étape), vous avez un ordre initial possible d’initialisation statique problème.

4) Répétez les étapes 2 et 3 jusqu'à épuisement de la liste générée à l'étape 1.

Remarque: vous pourrez peut-être optimiser cela en visitant uniquement l'arborescence des fonctions potentielles une fois par classe d'objet plutôt qu'une fois par instance globale si vous avez plusieurs globals d'une même classe.

4
Adisak

Remplacez tous les objets globaux par des fonctions globales qui renvoient une référence à un objet déclaré statique dans la fonction. Ceci n'est pas thread-safe, donc si votre application est multi-threadée, vous aurez peut-être besoin de quelques astuces comme pthread_once ou un verrou global. Cela garantira que tout est initialisé avant son utilisation.

Maintenant, soit votre programme fonctionne (hourra!), Soit il se trouve dans une boucle infinie parce que vous avez une dépendance circulaire (refonte nécessaire), ou bien vous passez au bogue suivant.

2
Steve Jessop

La première chose à faire est de dresser une liste de tous les objets statiques ayant des constructeurs non triviaux.

Dans ce cas, vous devez soit les brancher un par un, soit simplement les remplacer par des objets à motif singleton.

Le modèle de singleton suscite beaucoup de critiques, mais la construction paresseuse "à la demande" est un moyen assez facile de résoudre la plupart des problèmes actuels et futurs.

vieux...

MyObject myObject

nouveau...

MyObject &myObject()
{
  static MyObject myActualObject;
  return myActualObject;
}

Bien sûr, si votre application est multi-threadée, cela peut vous causer plus de problèmes que vous n'en aviez au départ ...

1
Roddy

Gimpel Software (www.gimpel.com) affirme que ses outils d'analyse statique PC-Lint/FlexeLint détecteront de tels problèmes.

J'ai eu une bonne expérience avec leurs outils, mais pas avec ce problème spécifique, je ne peux donc pas garantir à quel point ils pourraient m'aider.

1
Martin Vuille

D'autres réponses sont correctes, je voulais juste ajouter que le getter de l'objet devrait être implémenté dans un fichier .cpp et qu'il ne devrait pas être statique. Si vous l'implémentez dans un fichier d'en-tête, l'objet sera créé dans chaque bibliothèque/infrastructure à partir de laquelle vous l'appelez ....

0
Ryan

Si votre projet est dans Visual Studio (j'ai déjà essayé avec VC++ Express 2005 et Visual Studio 2008 Pro):

  1. Ouvrir la vue Classe (Menu principal-> Vue-> Vue Classe)
  2. Développez chaque projet dans votre solution et cliquez sur "Variables et fonctions globales".

Cela devrait vous donner une liste décente de tous les globals qui sont soumis à le fiasco .

En fin de compte, une meilleure approche consiste à essayer de supprimer ces objets de votre projet (parfois plus facile à dire qu'à faire).

0
Warren Stevens