webentwicklung-frage-antwort-db.com.de

Probleme mit der statischen C++ - Initialisierungsreihenfolge finden

Wir haben einige Probleme mit dem static Initialisierungsreihenfolge-Fiasko , und ich bin auf der Suche nach Wegen, eine ganze Menge Code zu durchforsten, um mögliche Vorkommen zu finden. Irgendwelche Vorschläge, wie das effizient geht?

Edit: Ich bekomme ein paar gute Antworten, wie man das Problem der statischen Initialisierungsreihenfolge löst, aber das ist nicht wirklich meine Frage. Ich möchte wissen, wie man Objekte findet, die diesem Problem unterliegen. Evans Antwort scheint diesbezüglich die beste zu sein; Ich glaube nicht, dass wir valgrind verwenden können, aber wir verfügen möglicherweise über Tools zur Speicheranalyse, die eine ähnliche Funktion ausführen könnten. Das würde Probleme nur dann auffangen, wenn die Initialisierungsreihenfolge für einen bestimmten Build falsch ist und sich die Reihenfolge bei jedem Build ändern kann. Möglicherweise gibt es ein statisches Analysewerkzeug, das dies erfasst. Unsere Plattform ist ein IBM XLC/C++ - Compiler, der unter AIX ausgeführt wird.

55
Fred Larson

Reihenfolge der Initialisierung lösen:

Zunächst einmal ist dies nur eine vorübergehende Umgehung, da Sie globale Variablen haben, die Sie loswerden möchten, aber noch keine Zeit hatten (Sie werden sie irgendwann loswerden, nicht wahr? :-)

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

Dies garantiert, dass es bei der ersten Verwendung initialisiert und bei Beendigung der Anwendung gelöscht wird.

Problem mit mehreren Threads:

C++ 11 do garantiert, dass dies Thread-sicher ist:

§6.7 [stmt.dcl] p4
Wenn die Steuerung die Deklaration gleichzeitig eingibt, während die Variable initialisiert wird, muss die gleichzeitige Ausführung auf den Abschluss der Initialisierung warten.

C++ 03 garantiert jedoch nicht offiziell, dass die Konstruktion statischer Funktionsobjekte threadsicher ist. Technisch muss also die getInstance_XXX()-Methode mit einem kritischen Abschnitt überwacht werden. Auf der positiven Seite hat gcc einen expliziten Patch als Teil des Compilers, der garantiert, dass jedes statische Funktionsobjekt selbst bei Threads nur einmal initialisiert wird.

Bitte beachten Sie: Nicht Verwenden Sie das doppelt geprüfte Sperrmuster , um die Kosten für das Sperren zu vermeiden. Dies wird in C++ 03 nicht funktionieren.

Probleme bei der Erstellung:

Bei der Erstellung gibt es keine Probleme, da wir garantieren, dass sie erstellt wird, bevor sie verwendet werden kann.

Zerstörungsprobleme:

Es besteht ein potenzielles Problem beim Zugriff auf das Objekt, nachdem es zerstört wurde. Dies geschieht nur, wenn Sie vom Destruktor einer anderen globalen Variablen auf das Objekt zugreifen (global bezieht sich hier auf jede nichtlokale statische Variable).

Die Lösung besteht darin, sicherzustellen, dass Sie die Reihenfolge der Zerstörung erzwingen.
.__ Denken Sie daran, dass die Zerstörungsreihenfolge die genaue Umkehrung der Konstruktionsreihenfolge ist. Wenn Sie also auf das Objekt in Ihrem Destruktor zugreifen, müssen Sie sicherstellen, dass das Objekt nicht zerstört wurde. Dazu müssen Sie nur sicherstellen, dass das Objekt vollständig erstellt ist, bevor das aufrufende Objekt erstellt wird.

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

Ich habe gerade etwas Code geschrieben, um dieses Problem zu finden. Wir haben eine gute Codebasis (1000 Dateien), die unter Windows/VC++ 2005 einwandfrei funktionierte, beim Systemstart unter Solaris/gcc .. __ abstürzte. Ich schrieb die folgende .h-Datei:

#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

und in every .cpp-Datei in der Projektmappe fügte ich Folgendes hinzu:

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

Wenn Sie Ihre Anwendung ausführen, erhalten Sie eine Ausgabedatei wie folgt:

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]

Bei einem Absturz sollte sich der Täter in der letzten .cpp-Datei befinden. Zumindest ist dies ein guter Ort für das Setzen von Haltepunkten, da dieser Code der absolute erste [] Ihres Codes sein sollte, der ausgeführt werden soll. Danach können Sie Ihren Code schrittweise durchlaufen und alle Globals sehen werden initialisiert).

Anmerkungen:

  • Es ist wichtig, dass Sie das Makro "FIASCO_Finder" so nah wie möglich an der Spitze Ihrer Datei platzieren. Wenn Sie einige andere # enthalten, gehen Sie das Risiko ein, dass es abstürzt, bevor Sie die Datei identifizieren, in der Sie sich befinden.

  • Wenn Sie Visual Studio und vorkompilierte Header verwenden, können Sie diese zusätzliche Makrozeile zu all Ihrer .cpp-Dateien schnell hinzufügen, indem Sie den Dialog Suchen und Ersetzen verwenden, um den vorhandenen Vorkompilierten Header #include "zu ersetzen .h "mit dem gleichen Text und der FIASCO_Finder-Zeile (Wenn Sie reguläre Ausdrücke abhaken, können Sie"\n "verwenden, um mehrzeiligen Ersetzungstext einzufügen.) 

30
Warren Stevens

Abhängig von Ihrem Compiler können Sie am Konstruktor-Initialisierungscode einen Haltepunkt setzen. In Visual C++ ist dies die _initterm-Funktion, die einen Start- und Endzeiger einer Liste der aufzurufenden Funktionen erhält.

Treten Sie dann in jede Funktion ein, um den Datei- und Funktionsnamen abzurufen (vorausgesetzt, Sie haben mit dem Debugging-Info on kompiliert). Sobald Sie die Namen haben, verlassen Sie die Funktion (bis _initterm) und fahren Sie fort, bis _initterm beendet ist.

Das gibt Ihnenalldie statischen Initialisierer, nicht nur die in Ihrem Code - es ist der einfachste Weg, eine erschöpfende Liste zu erhalten. Sie können diejenigen herausfiltern, auf die Sie keinen Einfluss haben (z. B. in Bibliotheken von Drittanbietern).

Die Theorie gilt für andere Compiler, der Name der Funktion und die Funktionalität des Debuggers können sich jedoch ändern.

14
paxdiablo

Es gibt Code, der im Wesentlichen C++ "initialisiert", das vom Compiler generiert wird. Um diesen Code bzw. den Aufrufstapel zu finden, können Sie auf einfache Weise ein statisches Objekt mit etwas erstellen, das im Konstruktor NULL dereferenziert - im Debugger aufbrechen und etwas erforschen. Der MSVC-Compiler richtet eine Tabelle von Funktionszeigern ein, die zur statischen Initialisierung durchlaufen werden. Sie sollten in der Lage sein, auf diese Tabelle zuzugreifen und alle statischen Initialisierungen zu bestimmen, die in Ihrem Programm stattfinden.

5
Ben Murrell

verwenden Sie möglicherweise valgrind, um die Verwendung von nicht initialisiertem Speicher zu finden. Die schönste Lösung für das "Fiasko der statischen Initialisierungsreihenfolge" ist die Verwendung einer statischen Funktion, die eine Instanz des Objekts wie folgt zurückgibt:

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

Auf diese Weise können Sie auf Ihr statisches Objekt zugreifen, indem Sie getStatic aufrufen. Dies garantiert, dass es bei der ersten Verwendung initialisiert wird. 

Wenn Sie sich um die Reihenfolge der De-Initialisierung kümmern müssen, geben Sie ein neues Objekt anstelle eines statisch zugewiesenen Objekts zurück. 

BEARBEITEN: Das redundante statische Objekt wurde entfernt. Ich weiß nicht warum, aber ich habe zwei Methoden miteinander kombiniert, um eine statische Einheit in meinem ursprünglichen Beispiel zu haben.

5
Evan Teran

Wir haben Probleme mit dem Fiasko der statischen Initialisierungsreihenfolge, und ich suche nach Möglichkeiten, durch eine ganze Menge Code zu finden mögliche Vorkommen. Irgendwelche Vorschläge wie geht das effizient?

Dies ist kein triviales Problem, aber es kann zumindest mit relativ einfachen Schritten bewerkstelligt werden, wenn Sie eine leicht zu parse mittelgroße Darstellung Ihres Codes haben.

1) Finden Sie alle Globals, die nicht triviale Konstruktoren haben, und stellen Sie sie in eine Liste.

2) Generieren Sie für jedes dieser nicht trivial konstruierten Objekte den gesamten Potenzialfunktionsbaum, der von ihren Konstruktoren aufgerufen wird.

3) Gehen Sie durch die Funktionsstruktur des nicht-trivial-Konstruktors. Wenn der Code auf andere nicht-trivial konstruierte Globals verweist (die in der Liste, die Sie in Schritt 1 erstellt haben, recht einfach sind), haben Sie möglicherweise eine frühe statische Initialisierungsreihenfolge Problem.

4) Wiederholen Sie die Schritte 2 und 3, bis Sie die in Schritt 1 erstellte Liste aufgebraucht haben.

Hinweis: Möglicherweise können Sie dies optimieren, indem Sie den Potenzialfunktionsbaum nur einmal pro Objektklasse und nicht einmal pro globaler Instanz aufrufen, wenn Sie mehrere Globale einer einzelnen Klasse haben.

4
Adisak

Ersetzen Sie alle globalen Objekte durch globale Funktionen, die einen Verweis auf ein in der Funktion als statisch deklariertes Objekt zurückgeben. Dies ist nicht Thread-sicher. Wenn Ihre App also mehrere Threads umfasst, benötigen Sie möglicherweise einige Tricks wie pthread_once oder eine globale Sperre. Dadurch wird sichergestellt, dass alles initialisiert wird, bevor es verwendet wird.

Jetzt funktioniert entweder Ihr Programm (Hurra!) Oder es sitzt in einer Endlosschleife, weil Sie eine zirkuläre Abhängigkeit haben (Neugestaltung erforderlich), oder Sie fahren mit dem nächsten Fehler fort.

2
Steve Jessop

Als Erstes müssen Sie eine Liste aller statischen Objekte erstellen, die über nicht triviale Konstruktoren verfügen.

In Anbetracht dessen müssen Sie sie entweder einzeln durchstecken oder einfach durch Singleton-Pattern-Objekte ersetzen.

Das Singleton-Muster stößt auf viel Kritik, aber die faule "bedarfsgerechte" Konstruktion ist eine relativ einfache Möglichkeit, die meisten Probleme jetzt und in der Zukunft zu lösen.

alt...

MyObject myObject

neu...

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

Wenn Ihre Anwendung Multithreading ist, kann dies natürlich mehr Probleme verursachen, als Sie es anfangs hatten ...

1
Roddy

Gimpel Software (www.gimpel.com) behauptet, dass ihre statischen PC-Lint/FlexeLint-Analysewerkzeuge solche Probleme erkennen werden.

Ich habe gute Erfahrungen mit ihren Werkzeugen gemacht, aber nicht mit diesem speziellen Problem. Daher kann ich nicht garantieren, wie viel sie helfen würden.

1
Martin Vuille

Andere Antworten sind richtig, ich wollte nur hinzufügen, dass der Getter des Objekts in einer .cpp-Datei implementiert werden sollte und nicht statisch sein sollte. Wenn Sie es in einer Headerdatei implementieren, wird das Objekt in jeder Bibliothek/jedem Framework erstellt, aus dem Sie es aufrufen.

0
Ryan

Wenn sich Ihr Projekt in Visual Studio befindet (ich habe dies mit VC++ Express 2005 und mit Visual Studio 2008 Pro versucht):

  1. Klassenansicht öffnen (Hauptmenü-> Ansicht-> Klassenansicht)
  2. Erweitern Sie jedes Projekt in Ihrer Lösung und klicken Sie auf "Globale Funktionen und Variablen".

Dies sollte Ihnen eine anständige Liste aller Globals geben, die dem Fiasko unterliegen.

Am Ende ist es ein besserer Ansatz, diese Objekte aus Ihrem Projekt zu entfernen (einfacher gesagt als getan, manchmal).

0
Warren Stevens