Partilhar via


Inicialização CRT

Este artigo descreve como o CRT inicializa o estado global em código nativo.

Por padrão, o vinculador inclui a biblioteca CRT, que fornece seu próprio código de inicialização. Esse código de inicialização inicializa a biblioteca CRT, chama inicializadores globais e, em seguida, chama a função main fornecida pelo usuário para aplicativos de console.

É possível, embora não recomendado, aproveitar o comportamento do vinculador específico da Microsoft para inserir seus próprios inicializadores globais em uma ordem específica. Este código não é portátil e tem algumas ressalvas importantes.

Inicializando um objeto global

Considere o seguinte código C++ (C não permitirá esse código porque não permite uma chamada de função em uma expressão constante).

int func(void)
{
    return 3;
}

int gi = func();

int main()
{
    return gi;
}

Segundo o padrão C/C++, func() deve ser chamado antes que main() seja executado. Mas, quem chama?

Uma maneira de determinar o chamador é definir um ponto de interrupção em func(), depurar o aplicativo e examinar a pilha. Isso é possível porque o código-fonte CRT está incluído no Visual Studio.

Ao navegar pelas funções na pilha, você visualizará que o CRT está chamando uma lista de ponteiros de função. Essas funções são semelhantes a func() ou construtores de instâncias de classe.

A CRT obtém a lista de ponteiros de função do compilador C++ da Microsoft. Quando o compilador visualiza um inicializador global, ele gera um inicializador dinâmico na seção .CRT$XCU em que CRT é o nome da seção e XCU é o nome do grupo. Para obter uma lista de inicializadores dinâmicos, execute o comando dumpbin /all main.obj e pesquise a seção .CRT$XCU. O comando só se aplica quando main.cpp é compilado como um arquivo C++, não um arquivo C. Ele deve ser semelhante a este exemplo:

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))

A CRT define dois ponteiros:

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

Nenhum dos dois grupos tem outros símbolos definidos, exceto __xc_a e __xc_z.

Agora, quando o vinculador lê várias subseções .CRT (o bloco após o $), ele as combina em uma seção e as classifica em ordem alfabética. Isso significa que os inicializadores globais definidos pelo usuário (que o compilador C++ da Microsoft coloca em .CRT$XCU) sempre aparecem depois de .CRT$XCA e antes de .CRT$XCZ.

A seção deve ser semelhante a este exemplo:

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

A biblioteca CRT usa e __xc_a __xc_z para determinar o início e o fim da lista global de inicializadores devido à maneira como eles são dispostos na memória depois que a imagem é carregada.

Recursos do vinculador para inicialização

O C++ Standard não fornece uma maneira compatível de especificar a ordem relativa entre unidades de tradução para um inicializador global fornecido pelo usuário. No entanto, como o vinculador da Microsoft ordena as subseções .CRT em ordem alfabética, é possível aproveitar essa ordenação para especificar a ordem de inicialização. Não recomendamos essa técnica específica da Microsoft e ela pode ser interrompida em uma versão futura. Documentamos isso apenas para impedir que você crie um código que está quebrado de maneiras difíceis de diagnosticar.

Para ajudar a evitar problemas em seu código, começando no Visual Studio 2019 versão 16.11, adicionamos dois novos avisos desativados por padrão: C5247 e C5248. Habilite esses avisos para detectar problemas ao criar seus próprios inicializadores.

Você pode adicionar inicializadores a nomes de seções reservadas não utilizados para criá-los em uma ordem relativa específica aos inicializadores dinâmicos gerados pelo 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;

Os nomes .CRT$XCT e .CRT$XCV não são usados pelo compilador ou pela biblioteca CRT no momento, mas não há garantia de que eles permanecerão não utilizados no futuro. E suas variáveis ainda podem ser otimizadas pelo compilador. Considere os possíveis problemas de engenharia, manutenção e portabilidade antes de adotar essa técnica.

Confira também

_initterm, _initterm_e
Arquivos .lib de runtime do C (CRT) e Biblioteca Padrão (STL) do C++