c++ - segun - meme y memetica
orden de inicialización estático fiasco (4)
Estaba leyendo sobre SIOF en un libro y dio un ejemplo:
//file1.cpp
extern int y;
int x=y+1;
//file2.cpp
extern int x;
int y=x+1;
Ahora mi pregunta es:
En el código anterior, ¿sucederán las siguientes cosas?
- Al compilar file1.cpp, el compilador deja y como está, es decir, no le asigna almacenamiento.
- el compilador asigna almacenamiento para x, pero no lo inicializa.
- Mientras compila file2.cpp, el compilador deja x como está, es decir, no le asigna almacenamiento.
- el compilador asigna almacenamiento para y, pero no lo inicializa.
- Al vincular file1.o y file2.o, ahora primero se inicializa file2.o, así que ahora:
¿Obtiene x el valor inicial de 0? o no se inicializa?
El punto entero (y la razón por la que se llama "fiasco") es que es imposible decir con certeza qué sucederá en un caso como este. Esencialmente, estás pidiendo algo imposible (cada una de las dos variables es una mayor que la otra). Ya que no pueden hacer eso, lo que harán estará abierto a alguna pregunta: podrían producir 0/1, 1/0, 1/2, o 2/1, o posiblemente (el mejor de los casos) solo un error. mensaje.
Es dependiente del compilador y puede ser dependiente del tiempo de ejecución. Un compilador puede decidir la inicialización perezosa de variables estáticas cuando se accede a la primera variable de un archivo, o al acceder a cada variable. De lo contrario, se inicializarán todas las variables estáticas por archivo en el momento del lanzamiento, y el orden dependerá generalmente del orden de enlace de los archivos. El orden de los archivos podría cambiar según las dependencias u otras influencias dependientes del compilador.
Las variables estáticas generalmente se inicializan a cero a menos que tengan un inicializador constante. De nuevo, esto es dependiente del compilador. Entonces, una de estas variables probablemente será cero cuando la otra se inicialice. Sin embargo, como ambos tienen inicializadores, algunos compiladores pueden dejar los valores sin definir.
Creo que el escenario más probable sería:
- Se asigna espacio para las variables, y ambas tienen el valor 0.
- Una variable, digamos x, se inicializa y se establece en el valor 1.
- El otro, digamos y, se inicializa y se establece en el valor 2.
Siempre se podría ejecutar y ver. Puede ser que algunos compiladores generen código que entra en un bucle infinito.
Los pasos de inicialización se dan en 3.6.2 "Inicialización de objetos no locales" del estándar C ++:
Paso 1: x
e y
se inicializan en cero antes de que tenga lugar cualquier otra inicialización.
Paso 2: x
o y
se inicializa dinámicamente, cuál no está especificado por el estándar. Esa variable obtendrá el valor 1
ya que la otra variable se habrá inicializado con cero.
Paso 3: la otra variable se inicializará dinámicamente, obteniendo el valor 2
.
SIOF es en gran medida un artefacto de tiempo de ejecución, el compilador y el enlazador no tienen mucho que ver con eso. Considere la función atexit (), registra las funciones que se deben llamar al salir del programa. Muchas implementaciones de CRT tienen algo similar para la inicialización del programa, llamémoslo atinit ().
La inicialización de estas variables globales requiere la ejecución de un código, el compilador no puede determinar el valor. Así que el compilador genera fragmentos de código de máquina que ejecutan la expresión y asignan el valor. Estos fragmentos de código deben ejecutarse antes de que se ejecute main ().
Ahí es donde entra en juego atinit (). Una implementación de CRT común recorre una lista de punteros de función atinit y ejecuta los fragmentos de inicialización, en orden. El problema es el orden en que se registran las funciones en la lista atinit (). Si bien atexit () tiene un orden LIFO bien definido, y está determinado implícitamente por el orden en que el código llama atexit (), este no es el caso de las funciones atinit. La especificación de idioma no requiere un pedido, no hay nada que pueda hacer en su código para especificar un pedido. SIOF es el resultado.
Una posible implementación es el compilador que emite punteros de función en una sección separada. El enlazador los fusiona, produciendo la lista de atinit. Si su compilador hace eso, entonces el orden de inicialización será determinado por el orden en que usted vincula los archivos de objeto. Mire el archivo de mapa, debería ver la sección atinit si su compilador hace esto. No se llamará atinit, pero es probable que exista algún tipo de nombre con "init". Echando un vistazo al código fuente de CRT que llama main () también debería dar una idea.