fiasco da ordem de inicialização estática
-
27-09-2019 - |
Pergunta
Eu estava lendo sobre o SIOF em um livro e deu um exemplo:
//file1.cpp
extern int y;
int x=y+1;
//file2.cpp
extern int x;
int y=x+1;
Agora minha pergunta é:
No código acima, as seguintes coisas acontecerão?
- ao compilar o arquivo1.cpp, o compilador deixa y como está, ou seja, não aloca armazenamento para ele.
- o compilador aloca armazenamento para x, mas não o inicializa.
- Ao compilar file2.cpp, o compilador deixa x como está, ou seja, não aloca armazenamento para ele.
- o compilador aloca armazenamento para y, mas não o inicializa.
- Ao vincular o arquivo1.o e o arquivo2.o, agora deixe o arquivo2.o ser inicializado primeiro, então agora:
X obtém valor inicial de 0?ou não é inicializado?
Solução
As etapas de inicialização são fornecidas em 3.6.2 "Inicialização de objetos não locais" do padrão C ++:
Passo 1: x
e y
são zero inicializados antes que qualquer outra inicialização ocorra.
Passo 2: x
ou y
é dinamicamente inicializado - qual não é especificado pelo padrão. Essa variável receberá o valor 1
uma vez que a outra variável terá sido inicializada zero.
Etapa 3: a outra variável será inicializada dinamicamente, obtendo o valor 2
.
Outras dicas
SIOF é basicamente um artefato de tempo de execução, o compilador e o vinculador não têm muito a ver com isso.Considere a função atexit(), ela registra funções a serem chamadas na saída do programa.Muitas implementações de CRT possuem algo semelhante para inicialização de programas, vamos chamá-lo de atinit().
A inicialização dessas variáveis globais requer a execução de código, o valor não pode ser determinado pelo compilador.Assim, o compilador gera trechos de código de máquina que executam a expressão e atribuem o valor.Esses trechos precisam ser executados antes da execução de main().
É aí que entra atinit().Uma implementação CRT comum percorre uma lista de ponteiros de função atinit e executa os trechos de inicialização, em ordem.O problema é a ordem em que as funções são registradas na lista atinit().Embora atexit() tenha uma ordem LIFO bem definida e seja implicitamente determinada pela ordem em que o código chama atexit(), esse não é o caso das funções atinit.A especificação da linguagem não requer um pedido, não há nada que você possa fazer no seu código para especificar um pedido.O SIOF é o resultado.
Uma implementação possível é o compilador emitir ponteiros de função em uma seção separada.O vinculador os mescla, produzindo a lista atinit.Se o seu compilador fizer isso, a ordem de inicialização será determinada pela ordem em que você vincula os arquivos do objeto.Olhe para o arquivo de mapa, você deverá ver a seção atinit se o seu compilador fizer isso.Não será chamado de atinit, mas é provável que haja algum tipo de nome com "init".Dar uma olhada no código-fonte do CRT que chama main() também deve fornecer informações.
A questão toda (e a razão pela qual é chamado de "fiasco") é que é impossível dizer com certeza o que acontecerá em um caso como este.Essencialmente, você está pedindo algo impossível (que duas variáveis sejam cada uma maior que a outra).Como eles não podem fazer isso, o que farão está aberto a algumas questões - eles podem produzir 0/1, ou 1/0, ou 1/2, ou 2/1, ou possivelmente (na melhor das hipóteses) apenas um erro mensagem.
Depende do compilador e pode depender do tempo de execução.Um compilador pode decidir inicializar variáveis estáticas lentamente quando a primeira variável em um arquivo é acessada ou quando cada variável é acessada.Caso contrário, ele inicializará todas as variáveis estáticas por arquivo no momento da inicialização, com a ordem geralmente dependendo da ordem dos links dos arquivos.A ordem dos arquivos pode mudar com base nas dependências ou outras influências dependentes do compilador.
Variáveis estáticas geralmente são inicializadas com zero, a menos que tenham um inicializador constante.Novamente, isso depende do compilador.Portanto, uma dessas variáveis provavelmente será zero quando a outra for inicializada.No entanto, como ambos possuem inicializadores, alguns compiladores podem deixar os valores indefinidos.
Acho que o cenário mais provável seria:
- O espaço é alocado para as variáveis e ambas possuem o valor 0.
- Uma variável, digamos x, é inicializada e definida com o valor 1.
- O outro, digamos y, é inicializado e definido com o valor 2.
Você sempre pode executá-lo e ver.Pode ser que alguns compiladores gerem código que entre em loop infinito.