2013-12-15 13 views
10

Hat gcc irgendwelche Garantien bezüglich der Startzeit von statischen Elementen, insbesondere in Bezug auf Vorlagenklassen?Nicht verzögerte statische Elementinitialisierung für Vorlagen in gcc?

Ich möchte wissen, ob ich eine harte Garantie bekommen kann, dass statische Mitglieder (PWrap_T<T>::p_s) vor main() initialisiert werden, wenn Klassen über mehrere Kompilierungseinheiten instanziiert werden. Es ist nicht praktisch zu versuchen, ein Symbol von jeder Kompilierungseinheit am Anfang von main manuell zu berühren, aber es ist mir nicht klar, dass irgendetwas anderes funktionieren würde.

Ich habe mit Methoden wie bar() in verschiedenen Einheiten getestet und immer das gewünschte Ergebnis bekommen, aber ich muß wissen, wann/ob jemals gcc den Teppich ausreißen und ob es vermeidbar.

Werden außerdem alle statischen Member von einem DSO initialisiert, bevor eine Bibliothek geladen wird?

#include <iostream> 
#include <deque> 

struct P; 
inline std::deque<P *> &ps() { static std::deque<P *> d; return d; } 
void dump(); 

struct P { 
    P(int id, char const *i) : id_(id), inf_(i) { ps().push_back(this); } 
    void doStuff() { std::cout << id_ << " (" << inf_ << ")" << std::endl; } 
    int const  id_; 
    char const *const inf_; 
}; 

template <class T> 
struct PWrap_T { static P p_s; }; 

// *** Can I guarantee this is done before main()? *** 
template <class T> 
P PWrap_T<T>::p_s(T::id(), T::desc()); 

#define PP(ID, DESC, NAME) /* semicolon must follow! */ \ 
struct ppdef_##NAME {         \ 
    constexpr static int   id() { return ID; }  \ 
    constexpr static char const *desc() { return DESC; } \ 
};              \ 
PWrap_T<ppdef_##NAME> const NAME 

// In a compilation unit apart from the template/macro header. 
void dump() { 
    std::cout << "["; 
    for (P *pp : ps()) { std::cout << " " << pp->id_ << ":" << pp->inf_; } 
    std::cout << " ]" << std::endl; 
} 

// In some compilation unit. 
void bar(int cnt) { 
    for (int i = 0; i < cnt; ++i) { 
    PP(2, "description", pp); 
    pp.p_s.doStuff(); 
    } 
} 

int main() { 
    dump(); 
    PP(3, "another", pp2); 
    bar(5); 
    pp2.p_s.doStuff(); 
} 

(C++ 11 §3.6.2/4 - [basic.start.init] :)

Es wird die Implementierung definiert, ob die dynamische Initialisierung eines nicht-lokalen Variable mit statischer Speicherdauer wird vor der ersten Anweisung von main ausgeführt. Wenn die Initialisierung auf einen Zeitpunkt nach der ersten Anweisung von main verschoben wird, soll sie vor der ersten odr-Verwendung (3.2) einer Funktion oder Variablen erfolgen, die in der gleichen Übersetzungseinheit wie die zu initialisierende Variable definiert ist.

... Eine nicht lokale Variable mit statischer Speicherdauer mit Initialisierung mit Nebeneffekten muss initialisiert werden, auch wenn sie nicht odr-used ist (3.2, 3.7.1).

Auch versuchen __attribute__ ((init_priority(int))) oder __attribute__ ((constructor)) für die Initialisierung der Vorlage Mitglieds ergab warning: attributes after parenthesized initializer ignored, und ich kenne keine anderen Tricks in Bezug auf statische Initialisierung.

Vielen Dank im Voraus an jeden, der mir eine Antwort darauf geben kann!

+0

Ich stelle mir die 'odr-use' Regel dynamische gemeinsame Objekte decken soll (DSOs), die möglicherweise Datei-scope-Objekte haben. Sie können natürlich nicht alles in einem DSO initialisieren, wenn es nach dem Start von 'dlopen()' eingefügt wird, aber theoretisch kann 'dlopen()' sicherstellen, dass alles im DSO initialisiert wird, bevor Sie etwas anderes im DSO aufrufen. Ich kann mir vorstellen, dass die Antwort letztlich von der ABI für jedes Betriebssystem/jede Architektur definiert wird, für die Sie sich zusammensetzen. –

+0

Das Singleton-Muster löst das Problem, oder? – lkanab

Antwort

2

Der Standard garantiert, dass statische Speicherdauerobjekte initialisiert werden, bevor Funktionen/Variablen in der gleichen Übersetzungseinheit wie Ihr Objekt von einer externen Quelle verwendet werden.

Der hier verwendete Text wurde für die Verwendung mit gemeinsam genutzten Bibliotheken entwickelt. Da shared libraries nach dem Start von main() dynamisch geladen werden können, muss die Sprachspezifikation flexibel genug sein, um damit umzugehen. Solange Sie jedoch von außerhalb der Übersetzungseinheit auf Ihr Objekt zugreifen, ist sichergestellt, dass es vor dem Zugriff erstellt wurde (es sei denn, Sie machen etwas pathologisches).

ABER Dies verhindert jedoch nicht, dass es vor der Initialisierung verwendet wird, wenn es im Konstruktor eines anderen statischen Speicherdauerobjekts in derselben Kompilierungseinheit verwendet wird.

Sie können jedoch leicht manuell garantieren, dass ein statisches Objekt korrekt initialisiert wird, bevor es mithilfe der folgenden Technik verwendet wird.

Ändern Sie einfach die statische Variable in eine statische Funktion. Dann deklarieren Sie innerhalb der Funktion ein statisches Member, das zurückgegeben wird. Sie können es also genauso verwenden wie zuvor (fügen Sie einfach () hinzu).

So hier erhalten Sie die Vorteile eines globalen (was auch immer sie sind). Sie erhalten garantierte Konstruktion/Zerstörung und Sie wissen, dass das Objekt korrekt gebaut wird, bevor es verwendet werden kann.

Die einzige Änderung, müssen Sie machen, ist:

void bar(int cnt) { 
    for (int i = 0; i < cnt; ++i) { 
    PP(2, "description", pp); 
    pp.p_s().doStuff(); 
    // ^^ Add the braces here. 
    } 
} 
+0

Nur Problem mit diesem ist, dass Funktion-Statik threadsafe (die Initialisierung von ihnen mindestens) in pre-C++ 11 iirc nicht ist. – Bwmat

+0

@Bwmat: True die Sprache garantiert es nicht vor C++ 11. Aber gcc (sogar vor C++ 11). –

+1

@Bwmat: http://gcc.gnu.org/ml/gcc-patches/2004-09/msg00265.html –

1

Wie Sie bereits festgestellt haben, garantiert der C++ - Standard nicht, dass "die dynamische Initialisierung einer nicht lokalen Variablen mit statischer Speicherdauer vor der ersten Anweisung von main erfolgt". Jedoch führt GCC eine solche Initialisierung vor dem Ausführen von main durch, wie in How Initialization Functions Are Handled beschrieben.

Das einzige Problem ist die Initialisierung von statischen Objekten aus freigegebenen Bibliotheken mit dlopen geladen. Diese werden erst beim Laden der Bibliothek initialisiert, aber Sie können nichts dagegen tun.