Ordem de boot de variables ​​estáticas

C ++ garante que as variables ​​em uma unidade de compilation (arquivo .cpp) sejam inicializadas na ordem da declaração. Para o número de unidades de compilation, esta regra funciona para cada uma separadamente (quero dizer, variables ​​estáticas fora das classs).

Mas a ordem de boot das variables ​​é indefinida em diferentes unidades de compilation.

Onde posso ver algumas explicações sobre este pedido para o gcc e MSVC (eu sei que confiar nisso é uma idéia muito ruim – é apenas para entender os problemas que podemos ter com o código legado ao mudar para o novo sistema operacional principal e diferente do GCC) ?

Como você diz, o pedido é indefinido em diferentes unidades de compilation.

Dentro da mesma unidade de compilation, a ordem é bem definida: a mesma ordem de definição.

Isso ocorre porque isso não é resolvido no nível da linguagem, mas no nível do vinculador. Então você realmente precisa verificar a documentação do vinculador. Embora eu realmente duvide que isso ajude de alguma maneira útil.

Para gcc: Confira ld

Descobri que até alterar a ordem dos arquivos de objects vinculados pode alterar a ordem de boot. Portanto, não é apenas o seu vinculador que você precisa se preocupar, mas como o vinculador é invocado pelo seu sistema de compilation. Mesmo tentar resolver o problema é praticamente um não iniciante.

Em geral, isso é apenas um problema ao inicializar globais que se referenciam uns aos outros durante sua própria boot (afetando apenas objects com construtores).

Existem técnicas para contornar o problema.

  • Inicialização lenta.
  • Contador de Schwarz
  • Coloque todas as variables ​​globais complexas dentro da mesma unidade de compilation.

Espero que a ordem do construtor entre os módulos seja principalmente uma function de qual ordem você passa os objects para o vinculador.

No entanto, o GCC permite que você use init_priority para especificar explicitamente a ordenação de códigos globais:

 class Thingy { public: Thingy(char*p) {printf(p);} }; Thingy a("A"); Thingy b("B"); Thingy c("C"); 

saídas ‘ABC’ como seria de esperar, mas

 Thingy a __attribute__((init_priority(300))) ("A"); Thingy b __attribute__((init_priority(200))) ("B"); Thingy c __attribute__((init_priority(400))) ("C"); 

saídas ‘BAC’.

Como você já sabe que não deve confiar nessas informações, a menos que seja absolutamente necessário, aqui vem. Minha observação geral em vários toolchains (MSVC, gcc / ld, clang / llvm, etc) é que a ordem na qual seus arquivos de object são passados ​​para o vinculador é a ordem em que eles serão inicializados.

Há exceções para isso, e eu não reivindico a todos eles, mas aqui estão os que eu corri para dentro de mim:

1) As versões do GCC anteriores a 4.7 são realmente inicializadas na ordem inversa da linha de link. Este ticket no GCC é quando a mudança aconteceu, e quebrou vários programas que dependiam da ordem de boot (incluindo a minha!).

2) No GCC e no Clang, o uso da prioridade da function do construtor pode alterar a ordem de boot. Observe que isso se aplica somente às funções que são declaradas como “construtores” (isto é, elas devem ser executadas exatamente como um construtor de object global seria). Eu tentei usar prioridades como essa e descobri que mesmo com a mais alta prioridade em uma function construtora, todos os construtores sem prioridade (por exemplo, objects globais normais, funções construtoras sem prioridade) serão inicializados primeiro . Em outras palavras, a prioridade é apenas relativa a outras funções com prioridades, mas os verdadeiros cidadãos de primeira class são aqueles sem prioridade. Para piorar, esta regra é efetivamente o oposto no GCC anterior a 4.7 devido ao ponto (1) acima.

3) No Windows, há uma function de ponto de input de biblioteca compartilhada (DLL) muito útil chamada DllMain () , que se definida, será executada com o parâmetro “fdwReason” igual a DLL_PROCESS_ATTACH diretamente após todos os dados globais terem sido inicializados e antes que o aplicativo consumidor tenha a chance de chamar qualquer function na DLL. Isso é extremamente útil em alguns casos, e não há absolutamente nenhum comportamento análogo a isso em outras plataformas com GCC ou Clang com C ou C ++. O mais próximo que você irá encontrar é fazer uma function construtora com prioridade (veja o ponto (2) acima), que absolutamente não é a mesma coisa e não funcionará para muitos dos casos de uso para os quais o DllMain () trabalha.

4) Se você estiver usando o CMake para gerar seus sistemas de compilation, o que geralmente faço, descobri que a ordem dos arquivos de origem de input será a ordem de seus arquivos de objects resultantes fornecidos ao vinculador. No entanto, muitas vezes seu aplicativo / DLL também está vinculando em outras bibliotecas, caso em que essas bibliotecas estarão na linha de link após seus arquivos de origem de input. Se você deseja que um de seus objects globais seja o primeiro a ser inicializado, então você está com sorte e pode colocar o arquivo de origem que contém esse object como o primeiro da lista de arquivos de origem. No entanto, se você quiser que um seja o último a inicializar (o que pode efetivamente replicar o comportamento de DllMain ()!), Então você pode fazer uma chamada para add_library () com aquele arquivo de origem para produzir uma biblioteca estática e adicionar a biblioteca estática resultante como a dependência do último link em seu target_link_libraries () chama seu aplicativo / DLL. Seja cauteloso quanto ao fato de que seu object global pode ser otimizado nesse caso e você pode usar o sinalizador –whole-archive para forçar o vinculador a não remover os símbolos não utilizados para esse pequeno arquivo morto específico.

Dica de encerramento

Para saber absolutamente a ordem de boot resultante do seu aplicativo / biblioteca compartilhada, passe -print-map para ld linker e grep para .init_array (ou no GCC anterior a 4.7, grep for .ctors). Todo construtor global será impresso na ordem em que será inicializado e lembre-se de que a ordem é oposta no GCC anterior a 4.7 (consulte o ponto (1) acima).

O fator motivador para escrever esta resposta é que eu precisava conhecer essa informação, não tinha outra escolha senão confiar na ordem de boot, e encontrei apenas partes esparsas dessas informações em outros posts e fóruns da internet. A maior parte foi aprendida através de muita experimentação, e espero que isso poupe algumas pessoas na hora de fazer isso!

http://www.parashift.com/c++-faq-lite/ctors.html#faq-10.12 – este link se move. Este é mais estável, mas você terá que procurar por ele.

edit: osgx forneceu um link melhor.

Além dos comentários de Martin, vindos de um background em C, sempre penso em variables ​​estáticas como parte do espaço do programa executável, incorporado e alocado no segmento de dados. Assim, as variables ​​estáticas podem ser consideradas como sendo inicializadas à medida que o programa é carregado, antes de qualquer código ser executado. A ordem exata em que isso acontece pode ser verificada observando o segmento de dados da saída do arquivo de mapa pelo vinculador, mas para a maioria das intenções e finalidades a boot é simultânea.

Edit: Dependendo da ordem de construção de objects estáticos é susceptível de ser não-portáteis e provavelmente deve ser evitado.

Se você realmente quiser saber a ordem final, eu recomendo que você crie uma class cujo construtor registra o registro de data e hora atual e crie várias instâncias estáticas da class em cada um de seus arquivos cpp para que você possa saber a ordem final de boot. Certifique-se de colocar um pouco de tempo consumindo a operação no construtor apenas para que você não obtenha o mesmo carimbo de hora para cada arquivo.