Pregunta

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

Pero, el orden de inicialización de las variables, no está definido en las diferentes unidades de compilación.

¿Dónde puedo ver algunas explicaciones acerca de este pedido para gcc y MSVC? diferente sistema operativo)?

¿Fue útil?

Solución

Como usted dice, el orden no está definido en las diferentes unidades de compilación.

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

Esto se debe a que esto no se resuelve a nivel de idioma sino a nivel de enlazador. Así que realmente necesitas revisar la documentación del enlazador. Aunque realmente dudo que esto ayude de alguna manera útil.

Para gcc: consulte ld

He descubierto que incluso cambiar el orden de los archivos de objetos que están vinculados puede cambiar el orden de inicialización. Por lo tanto, no es solo su vinculador el que debe preocuparse, sino cómo el sistema de compilación invoca el vinculador. Incluso tratar de resolver el problema es prácticamente un no arranque.

Esto generalmente es solo un problema al inicializar globales que se refieren entre sí durante su propia inicialización (por lo que solo afecta a objetos con constructores).

Existen técnicas para solucionar el problema.

  • Inicialización perezosa.
  • Schwarz Counter
  • Coloque todas las variables globales complejas dentro de la misma unidad de compilación.

  • Nota 1: globales:
    Se utiliza libremente para referirse a las variables de duración del almacenamiento estático que se inicializan potencialmente antes de main () .
  • Nota 2: Potencialmente
    En el caso general, esperamos que las variables de duración del almacenamiento estático se inicialicen antes de main, pero el compilador puede diferir la inicialización en algunas situaciones (las reglas son complejas, consulte el estándar para obtener más información).

Otros consejos

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

Sin embargo, GCC le permite use init_priority para especificar explícitamente el ordenamiento para los controladores globales:

class Thingy
{
public:
    Thingy(char*p) {printf(p);}
};

Thingy a("A");
Thingy b("B");
Thingy c("C");

genera 'ABC' como cabría esperar, pero

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

genera 'BAC'.

Como ya sabe que no debe 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 objetos al vinculador es el orden en que se inicializarán.

Hay excepciones a esto, y no reclamo todas, pero aquí están las que me encontré:

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 se rompió mucho de programas que dependían de la orden de inicialización (incluido el mío).

2) En GCC y Clang, uso de constructor La prioridad de la función 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 tal como lo sería un constructor de objetos global). He intentado usar prioridades como esta y descubrí que incluso con la prioridad más alta 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 verdaderos ciudadanos de primera clase son aquellos sin prioridad. Para empeorar las cosas, esta regla es efectivamente lo contrario en GCC antes de 4.7 debido al punto (1) anterior.

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

4) Si está utilizando CMake para generar sus sistemas de compilación, lo que a menudo hago, he encontrado que el orden de los archivos de origen de entrada será el orden de sus archivos de objeto resultantes dados al vinculador. Sin embargo, muchas veces su aplicación / DLL también está enlazando en otras bibliotecas, en cuyo caso esas bibliotecas estarán en la línea de enlace después de sus archivos de fuente de entrada. Si desea que uno de sus objetos globales sea el muy primero para inicializar, tiene suerte y puede poner el archivo fuente que contiene ese objeto como el primero en la lista de fuentes. archivos. Sin embargo, si está buscando que uno sea el último para inicializar (¡lo que puede replicar efectivamente el comportamiento de DllMain ()!), Puede hacer una llamada a add_library () con ese único archivo de origen para genere una biblioteca estática y agregue 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 - archive completo para forzar al vinculador a no eliminar los símbolos no utilizados para ese archivo de archivo pequeño específico.

Sugerencia de cierre

Para saber 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 (consulte el punto (1) más arriba).

El factor motivador para escribir esta respuesta es que necesitaba conocer esta información, no tenía otra opción más que confiar en el orden de inicialización, y solo encontré fragmentos de esta información en otras publicaciones de SO y foros de Internet. ¡La mayor parte se aprendió a través de mucha experimentación, y espero que esto les 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 one es más estable, pero tendrá que buscarlo.

edit: osgx proporcionó una mejor enlace .

Además de los comentarios de Martin, provenientes de un fondo C, siempre pienso en las variables estáticas como parte del espacio ejecutable, incorporado y asignado del programa en el segmento de datos. Por lo tanto, se puede considerar que las variables estáticas se inicializan a medida que se carga el programa, antes de que se ejecute cualquier código. El orden exacto en el que sucede esto se puede determinar observando el segmento de datos de salida del archivo de mapa por el vinculador, pero para la mayoría de los intentos y 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 crear una clase cuyo constructor registre la marca de tiempo actual y crear varias instancias estáticas de la clase en cada uno de tus archivos cpp para que puedas saber el orden final de inicialización. . Asegúrese de poner un poco de operación que requiere mucho tiempo en el constructor para que no obtenga la misma marca de tiempo para cada archivo.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top