6

Aquí hay algunas buenas preguntas y respuestas sobre el "fiasco de orden de inicialización estática", pero parece que he golpeado contra otra expresión de ella, especialmente fea porque no falla, pero pierde y pierde datos.Inicialización doble de un contenedor STL estático en una biblioteca C++

Tengo una biblioteca personalizada de C++ y una aplicación que enlaza con ella. Hay un contenedor STL estático en la biblioteca que registra todas las instancias de una clase. Esas instancias resultan ser variables estáticas en la aplicación.

Como resultado del "fiasco" (creo), obtenemos el contenedor lleno con las instancias de la aplicación durante la inicialización de la aplicación, luego la biblioteca se inicializa y el contenedor se reinicia (probablemente con pérdida de memoria), terminando solo con las instancias de la biblioteca.

Esta es la forma en que lo reprodujeron con código simplificado:

mylib.hpp:

#include <iostream> 
#include <string> 
#include <vector> 

using namespace std; 

class MyLibClass { 
    static vector<string> registry; 
    string myname; 
    public: 
    MyLibClass(string name); 
}; 

mylib.cpp:

#include "mylib.hpp" 

vector<string> MyLibClass::registry; 

MyLibClass::MyLibClass(string name) 
: myname(name) 
{ 
    registry.push_back(name); 
    for(unsigned i=0; i<registry.size(); i++) 
     cout << " ["<< i <<"]=" << registry[i]; 
    cout << endl; 
} 

MyLibClass l1("mylib1"); 
MyLibClass l2("mylib2"); 
MyLibClass l3("mylib3"); 

MyApp.cpp:

#include "mylib.hpp" 

MyLibClass a1("app1"); 
MyLibClass a2("app2"); 
MyLibClass a3("app3"); 

int main() { 
    cout << "main():" << endl; 
    MyLibClass m("main"); 
} 

Compilar los objetos con:

g++ -Wall -c myapp.cpp mylib.cpp 
g++ myapp.o mylib.o -o myapp1 
g++ mylib.o myapp.o -o myapp2 

Run myapp1:

$ ./myapp1 
[0]=mylib1 
[0]=mylib1 [1]=mylib2 
[0]=mylib1 [1]=mylib2 [2]=mylib3 
[0]=mylib1 [1]=mylib2 [2]=mylib3 [3]=app1 
[0]=mylib1 [1]=mylib2 [2]=mylib3 [3]=app1 [4]=app2 
[0]=mylib1 [1]=mylib2 [2]=mylib3 [3]=app1 [4]=app2 [5]=app3 
main(): 
[0]=mylib1 [1]=mylib2 [2]=mylib3 [3]=app1 [4]=app2 [5]=app3 [6]=main 

Run myapp2:

$ ./myapp2 
[0]=app1 
[0]=app1 [1]=app2 
[0]=app1 [1]=app2 [2]=app3 
[0]=mylib1 
[0]=mylib1 [1]=mylib2 
[0]=mylib1 [1]=mylib2 [2]=mylib3 
main(): 
[0]=mylib1 [1]=mylib2 [2]=mylib3 [3]=main 

Aquí viene la pregunta, el vector estática fue re-inicializado , o usado antes de la inicialización? ¿Es esto un comportamiento esperado?

Si 'ar' la biblioteca como 'mylib.a' (ar rcs mylib.a mylib.o), el problema no ocurre, pero probablemente porque solo hay un orden válido para enlazar con .a y es por tener la biblioteca en el último lugar, como para myapp1 aquí.

Pero en nuestra aplicación real, una más compleja con muchos archivos de objetos y algunas bibliotecas estáticas (.a) compartiendo unos pocos registros estáticos, el problema está sucediendo y la única manera en que logramos resolverlo es por aplicando '[10.15] How do I prevent the "static initialization order fiasco"?'.

(Todavía estoy investigando en nuestro sistema de compilación algo complejo para ver si estamos enlazando correctamente).

+1

Muy buen código de muestra por cierto. Ojalá todas las preguntas se vieran así. –

Respuesta

4

Una forma de evitar los problemas de orden de inicialización es mover las variables estáticas del ámbito global al ámbito local.

En lugar de tener una variable registry dentro de la clase, lo puso en una función:

vector<string> & MyLibClass::GetRegistry() 
{ 
    static vector<string> registry; 
    return registry; 
} 

En los lugares donde se habría utilizado registry directamente, tiene que llamar GetRegistry.

+0

Estoy eligiendo esta respuesta porque proporciona un código de muestra claro. Después de la investigación, parece ser también de Meyer, como se mencionó anteriormente. – gatopeich

+0

@gatopeich, sí lo busqué después de ver la otra respuesta, pero nunca supe que tenía un nombre. –

2

Si das vector<string> un constructor de encargo se verá, que de hecho es llamado una sola vez, pero en myapp2 está utilizando registry sin inicializar primero, y luego éste se inicializa ("eliminar" todo lo que hay dentro) y luego se llena de nuevo . Que no segfault es solo suerte :)

No puedo decir qué parte de la norma dice algo acerca de este comportamiento, pero en mi humilde opinión debe/nunca/deje que las variables estáticas dependan una de la otra. Puede usar un singleton de Meyers, por ejemplo, para el registro.

+0

+1 para Meyers singleton – wheaties

+0

De hecho, esa es la solución que elegimos, pero no sabía que era "Meyer's". – gatopeich

+0

Scott Meyers es el nombre. Creo que fue el primero en proponer esta implementación del singleton en su "C++ más efectivo". – filmor

0

Estás utilizando 2 técnicas conocidas.

(1) El "módulo/library/espacio de nombres" como un patrón "dispositivo"

(2) tipo de encargo de registro, con una clase estática.

Has hecho algo similar con "Object Pascal" y "Plain C". Tengo varios archivos, cada archivo funciona como un módulo/espacio de nombres, con typedefs, clases, funciones. Además, cada "espacio de nombre" tenía 2 métodos especiales (la misma firma o prototipo), que simulan la conexión de un dispositivo y la desconexión de un dispositivo. Ya intenté llamar a esos métodos automáticamente, pero el orden de ejecución también salió mal.

Las clases de Static, Singleton pueden convertirse en un desastre. Sugiero, olvídate de usar macros o preprocesador/compilador y llama al tus métodos de inicialización/finalización tú mismo.

---- 
mylib.hpp 
---- 

class MyLibClass { 
    public: 
    Register(string libraryName); 
    UnRegister(string libraryName); 
}; 

// don't execute the "custom type registration here" 

----- 
mynamespace01.cpp 
----- 
#include "mylib.hpp" 

void mynamespace01_otherstuff() { ... } 

// don't execute registration 
void mynamespace01_start() { 
    if not (MyLibClass::IsUnRegistered("mynamespace01")) MyLibClass::Register("mynamespace01"); 
} 

void mynamespace01_finish() 
{ 
    if not (MyLibClass::IsRegistered("mynamespace01")) MyLibClass::UnRegister("mynamespace01"); 
} 

----- 
mynamespace02.cpp 
----- 
#include "mylib.hpp" 

// check, "2" uses "1" !!! 
#include "mynamespace01.hpp" 

void mynamespace02_otherstuff() { ... } 

// don't execute registration !!! 
void mynamespace02_start() { 
    // check, "2" uses "1" !!! 
    void mynamespace01_start(); 

    if not (MyLibClass::IsUnRegistered("mynamespace01")) MyLibClass::Register("mynamespace02"); 

    void mynamespace02_start(); 
} 

void mynamespace02_finish(){ 
    void mynamespace02_finish(); 

    if not (MyLibClass::IsRegistered("mynamespace02")) MyLibClass::UnRegister("mynamespace02"); 

    // check, "2" uses "1" !!! 
    void mynamespace02_start(); 
} 

----- 
myprogram.cpp 
----- 

#include "mynamespace01.hpp" 
#include "mynamespace02.hpp" 

void myprogram_otherstuff() { ... } 

// don't execute registration !!! 
void myprogram_start() { 
    // check, "2" uses "1" !!! 
    mynamespace01_start(); 
    mynamespace02_start(); 

    if not (MyLibClass::IsUnRegistered("myprogram")) MyLibClass::Register("myprogram"); 
} 
void myprogram_finish() { 
    if not (MyLibClass::IsRegistered("myprogram")) MyLibClass::UnRegister("myprogram"); 

    // check, "2" uses "1" !!! 
    mynamespace01_finish(); 
    mynamespace02_finish(); 
} 

void main() { 
    // all registration goes here !!!: 

    // "device" initializers order coded by hand: 
    myprogram_start(); 

    // other code; 

    // "device" finalizers order inverse coded by hand: 
    myprogram_finish(); 
} 
----- 

Compruebe que este código es más complejo y detallado que el suyo, pero, en mi experiencia, es más estable.

También agregué el "finalizador" a "inicializador" y reemplazo el identificador de "Registrar".

Buena suerte.

Cuestiones relacionadas