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