Orden de inicialización de variables estáticas

C ++ garantiza que las variables en una unidad de comstackción (archivo .cpp) se inicialicen en orden de statement. Para el número de unidades de comstackción, esta regla funciona para cada una por separado (es decir, variables estáticas fuera de las clases).

Sin embargo, el orden de inicialización de las variables no está definido en las diferentes unidades de comstackción.

¿Dónde puedo ver algunas explicaciones sobre este orden para gcc y MSVC? (Sé que confiar en eso es una muy mala idea; solo es para entender los problemas que podemos tener con el código heredado cuando pasamos a un nuevo sistema principal de GCC y a un sistema operativo diferente) ?

Como dices, el orden no está definido en las diferentes unidades de comstackción.

Dentro de la misma unidad de comstackción, el orden está bien definido: el mismo orden que la definición.

Esto se debe a que esto no se resuelve en el nivel de idioma sino en el nivel del vinculador. Entonces realmente necesita verificar la documentación del enlazador. Aunque realmente dudo que esto ayude de alguna manera útil.

Para gcc: echa un vistazo a ld

Descubrí que incluso cambiar el orden de los archivos de objetos vinculados puede cambiar el orden de inicialización. Por lo tanto, no es solo su enlazador el que debe preocuparse, sino cómo su sistema de comstackción invoca el enlazador. Incluso tratar de resolver el problema prácticamente no es un comienzo.

En general, esto solo es un problema al inicializar global que hace referencia entre sí durante su inicialización (por lo que solo afecta a objetos con constructores).

Hay técnicas para evitar el problema.

  • Inicialización lenta
  • Contador Schwarz
  • Coloque todas las variables globales complejas dentro de la misma unidad de comstackción.

Espero que el orden del constructor entre los módulos sea principalmente una función del orden en que pasas los objetos al enlazador.

Sin embargo, GCC le permite usar init_priority para especificar explícitamente el orden para los ctors globales:

 class Thingy { public: Thingy(char*p) {printf(p);} }; Thingy a("A"); Thingy b("B"); Thingy c("C"); 

saca ‘ABC’ como es de esperar, pero

 Thingy a __attribute__((init_priority(300))) ("A"); Thingy b __attribute__((init_priority(200))) ("B"); Thingy c __attribute__((init_priority(400))) ("C"); 

salidas ‘BAC’.

Como ya sabes que no deberías confiar en esta información a menos que sea absolutamente necesario, aquí viene. Mi observación general a través de varias cadenas de herramientas (MSVC, gcc / ld, clang / llvm, etc.) es que el orden en que se pasan los archivos de objeto al vinculador es el orden en el que se inicializarán.

Hay excepciones a esto, y no reclamo a todos ellos, pero aquí están los que encontré yo mismo:

1) Las versiones de GCC anteriores a 4.7 realmente se inicializan en el orden inverso de la línea de enlace. Este boleto en GCC es cuando ocurrió el cambio, y rompió una gran cantidad de progtwigs que dependían del orden de inicialización (¡incluido el mío!).

2) En GCC y Clang, el uso de la prioridad de la función constructora puede alterar el orden de inicialización. Tenga en cuenta que esto solo se aplica a las funciones que se declaran como “constructores” (es decir, deben ejecutarse como lo haría un constructor de objetos globales). He intentado usar prioridades como esta y descubrí que incluso con la más alta prioridad en una función de constructor, todos los constructores sin prioridad (por ejemplo, objetos globales normales, funciones de constructor sin prioridad) se inicializarán primero . En otras palabras, la prioridad es solo relativa a otras funciones con prioridades, pero los ciudadanos de primera clase son aquellos sin prioridad. Para empeorar las cosas, esta regla es efectivamente lo opuesto en GCC antes de 4.7 debido al punto (1) anterior.

3) En Windows, hay una función de punto de entrada muy limpia y útil de biblioteca compartida (DLL) llamada DllMain () , que si se define, se ejecutará con el parámetro “fdwReason” igual a DLL_PROCESS_ATTACH directamente después de que se hayan inicializado todos los datos globales y antes de que la aplicación consumidora tenga la oportunidad de llamar a cualquier función en la DLL. Esto es extremadamente útil en algunos casos, y no existe un comportamiento análogo a este en otras plataformas con GCC o Clang con C o C ++. Lo más cerca que encontrará es hacer una función de constructor con prioridad (vea el punto (2) anterior), que no es lo mismo y no funcionará para muchos de los casos de uso para los que funciona DllMain ().

4) Si está utilizando CMake para generar sus sistemas de comstackción, lo cual hago a menudo, he encontrado que el orden de los archivos fuente de entrada será el orden de los archivos de objetos resultantes dados al enlazador. Sin embargo, a menudo su aplicación / DLL también se vincula en otras bibliotecas, en cuyo caso esas bibliotecas estarán en la línea de enlace después de sus archivos fuente de entrada. Si está buscando que uno de sus objetos globales sea el primero en inicializarse, entonces tiene suerte y puede poner el archivo fuente que contiene ese objeto para ser el primero en la lista de archivos fuente. Sin embargo, si está buscando que uno sea el último en inicializarse (¡que puede replicar efectivamente el comportamiento de DllMain ()!) Puede realizar una llamada a add_library () con ese único archivo fuente para generar una biblioteca estática y agregar la biblioteca estática resultante como la última dependencia de enlace en su llamada target_link_libraries () para su aplicación / DLL. Tenga cuidado de que su objeto global pueda optimizarse en este caso y puede usar el indicador –whole-archive para forzar al enlazador a no eliminar los símbolos no utilizados para ese pequeño archivo específico.

Consejo de cierre

Para conocer absolutamente el orden de inicialización resultante de su aplicación / biblioteca compartida vinculada, pase –print-map a ld linker y grep para .init_array (o en GCC antes de 4.7, grep para .ctors). Cada constructor global se imprimirá en el orden en que se inicializará, y recuerde que el orden es opuesto en GCC antes de 4.7 (vea el punto (1) anterior).

El factor motivador para escribir esta respuesta es que necesitaba saber esta información, no tuve más remedio que confiar en el orden de inicialización y encontré solo fragmentos dispersos de esta información en otras publicaciones de SO y foros de Internet. La mayor parte se aprendió mediante mucha experimentación, y espero que esto ahorre a algunas personas el tiempo de hacerlo.

http://www.parashift.com/c++-faq-lite/ctors.html#faq-10.12 – este enlace se mueve. este es más estable pero tendrás que buscarlo.

editar: osgx proporcionó un mejor enlace .

Además de los comentarios de Martin, provenientes de un entorno C, siempre pienso en variables estáticas como parte del progtwig ejecutable, espacio incorporado y asignado en el segmento de datos. Por lo tanto, se puede pensar que las variables estáticas se inicializan a medida que se carga el progtwig, antes de que se ejecute cualquier código. El orden exacto en el que esto sucede se puede determinar mirando el segmento de datos del archivo de mapa generado por el vinculador, pero para la mayoría de los propósitos, la inicialización es simultánea.

Editar: Dependiendo del orden de construcción de los objetos estáticos es probable que no sea portátil y probablemente deba evitarse.

Si realmente quieres saber el orden final, te recomendaría que crees una clase cuyo constructor registra la marca de tiempo actual y crea varias instancias estáticas de la clase en cada uno de tus archivos cpp para que puedas conocer el orden final de inicialización. Asegúrate de poner algo de tiempo en el constructor para que no obtengas la misma marca de tiempo para cada archivo.