Compartir a través de


Inicialización de CRT

En este artículo se describe cómo CRT inicializa estados globales en código nativo.

De forma predeterminada, el vinculador incluye la biblioteca de CRT, que proporciona su propio código de inicio. Este código de inicio inicializa la biblioteca de CRT, llama a los inicializadores globales y, después, llama a la función main proporcionada por el usuario para las aplicaciones de consola.

Aunque no se recomienda, es posible aprovechar el comportamiento del enlazador específico de Microsoft para insertar sus propios inicializadores globales en un orden específico. Este código no es portable y viene con algunas advertencias importantes.

Inicialización de un objeto global

Tenga en cuenta el siguiente código de C++ (C no permitirá este código porque no permite una llamada de función en una expresión constante).

int func(void)
{
    return 3;
}

int gi = func();

int main()
{
    return gi;
}

Según el estándar de C o C++, se debe llamar a func() antes de ejecutar main(). Pero, ¿quién llama?

Una forma de determinar el autor de la llamada es establecer un punto de interrupción en func(), depurar la aplicación y examinar la pila. Esto es posible porque el código fuente de CRT se incluye en Visual Studio.

Al examinar las funciones en la pila, observará que CRT llama a una lista de punteros de función. Estas funciones son similares a func(), o constructores de instancias de clase.

CRT obtiene la lista de punteros de función del compilador de Microsoft C++. Cuando el compilador encuentra un inicializador global, genera un inicializador dinámico en la sección .CRT$XCU (donde CRT es el nombre de sección y XCU es el nombre de grupo). Para obtener una lista de inicializadores dinámicos, ejecute el comando dumpbin /all main.obj y, luego, busque en la sección .CRT$XCU. El comando solo se aplica cuando main.cpp se compila como un archivo de C++, no como un archivo de C. Debe ser similar a este ejemplo:

SECTION HEADER #6
.CRT$XCU name
       0 physical address
       0 virtual address
       4 size of raw data
     1F2 file pointer to raw data (000001F2 to 000001F5)
     1F6 file pointer to relocation table
       0 file pointer to line numbers
       1 number of relocations
       0 number of line numbers
40300040 flags
         Initialized Data
         4 byte align
         Read Only

RAW DATA #6
  00000000: 00 00 00 00                                      ....

RELOCATIONS #6
                                               Symbol    Symbol
Offset    Type              Applied To         Index     Name
--------  ----------------  -----------------  --------  -------
00000000  DIR32             00000000           C         ??__Egi@@YAXXZ (void __cdecl `dynamic initializer for 'gi''(void))

CRT define dos punteros:

  • __xc_a en .CRT$XCA
  • __xc_z en .CRT$XCZ

Ninguno de los grupos tiene ningún otro símbolo definido excepto __xc_a y __xc_z.

Ahora, cuando el enlazador lee varias subsecciones .CRT (la parte después de $), las combina en una sección y las ordena alfabéticamente. Esto significa que los inicializadores globales definidos por el usuario (que el compilador de Microsoft C++ coloca en .CRT$XCU) siempre irán después de .CRT$XCA y antes de .CRT$XCZ.

La sección debe ser similar a este ejemplo:

.CRT$XCA
            __xc_a
.CRT$XCU
            Pointer to Global Initializer 1
            Pointer to Global Initializer 2
.CRT$XCZ
            __xc_z

La biblioteca de CRT usa y __xc_a __xc_z para determinar el inicio y el final de la lista de inicializadores globales debido a la forma en que se diseñan en la memoria después de cargar la imagen.

Características del enlazador para la inicialización

C++ estándar no proporciona una manera conforme de especificar el orden relativo entre las unidades de traducción para un inicializador global proporcionado por el usuario. Sin embargo, dado que el enlazador de Microsoft ordena alfabéticamente las subsecciones .CRT, es posible aprovechar esta ordenación para especificar el orden de inicialización. Esta técnica es específica de Microsoft y no se recomienda; podría eliminarse en una versión futura. La hemos documentado solo para evitar que cree código que se rompa de maneras difíciles de diagnosticar.

Para ayudar a evitar problemas en el código, a partir de la versión 16.11 de Visual Studio 2019, hemos agregado dos nuevas advertencias de forma predeterminada: C5247 y C5248. Habilite estas advertencias para detectar problemas al crear sus propios inicializadores.

Puede agregar inicializadores a nombres de sección reservados sin usar para crearlos en un orden relativo específico para los inicializadores dinámicos generados por el compilador:

#pragma section(".CRT$XCT", read)
// 'i1' is guaranteed to be called before any compiler generated C++ dynamic initializer
__declspec(allocate(".CRT$XCT")) type i1 = f;

#pragma section(".CRT$XCV", read)
// 'i2' is guaranteed to be called after any compiler generated C++ dynamic initializer
__declspec(allocate(".CRT$XCV")) type i2 = f;

Ahora mismo el compilador y la biblioteca CRT no usan los nombres .CRT$XCT y .CRT$XCV, pero no hay ninguna garantía de que permanezcan sin usar en el futuro. Además, el compilador podría optimizar las variables. Tenga en cuenta los posibles problemas de ingeniería, mantenimiento y portabilidad antes de adoptar esta técnica.

Consulte también

_initterm, _initterm_e
Archivos .lib de tiempo de ejecución de C (CRT) y biblioteca estándar de C++ (STL)