Tudo o que encontrei na Internet sobre o fiasco da ordem de inicialização estática era sobre C++, mas é verdade que se eu inicializar uma variável global de algum tipo Foo como
struct Foo {
int flag;
pthread_key_t key;
void *ptrs[10];
};
Não consigo inicializar uma variável do tipo struct Foo
como static struct Foo x = { 0 };
? se eu quiser obter o código correto por causa do SIOF?
O problema com a inicialização em C++ é que o código executável pode ser executado antes da
main
função, portanto, nem sempre é claro em que ordem esse código será executado. Isso é necessário em C++ devido aos construtores para objetos estáticos.C, por outro lado, não permite que o código seja executado fora de uma função. Os inicializadores para objetos estáticos devem ser expressões constantes que podem ser calculadas em tempo de compilação.
Isso significa que um inicializador como
static struct Foo x = { 0 };
é perfeitamente adequado em C.Para objetos com duração de armazenamento estático (e thread), C apenas afirma que eles são inicializados em algum ponto antes de main() ser chamado. C apenas permite que eles sejam inicializados com expressões constantes. Já em C++, os objetos podem ter construtores e podem ser inicializados com o resultado de uma função.
Se espiarmos "por baixo do capô" do código "C runtime" (CRT) que é executado antes de main() ser chamado, no que diz respeito às variáveis, ele inicializará apenas
.data
and.bss
. A partir daí, ele está pronto para funcionar. O tempo de execução C++ equivalente não é tão trivial, porque também inicia chamadas de construtor, etc. Como nenhuma ordem específica é especificada nem pelo padrão C++ nem pelo programador, o CRT apenas os chamará em alguma ordem subjetiva de aparência. Se houver dependências de ordem de inicialização entre objetos nesse ponto, tudo logo desmoronará.C++ também adicionou complexidade adicional ao definir a inicialização estática como tudo que se enquadra em duas subcategorias: inicialização constante e inicialização zero . E então nomeia todo o resto como inicialização dinâmica (não deve ser confundida com alocação dinâmica). A inicialização dinâmica, por sua vez, vem com conceitos de ordem de aparecimento, sequência e assim por diante.
No contexto de como você deseja inicializar sua estrutura com zero, não há problema.
No entanto, no caso geral, o problema pode ocorrer no código C ao abrir
.so
bibliotecas, também conhecidas como bibliotecas compartilhadas. Isso ocorre porque as bibliotecas compartilhadas podem incluir uma.init
seção de código que é executada quando a biblioteca é carregada.Então, você deve imaginar duas bibliotecas compartilhadas que se referem às estruturas de dados uma da outra dentro de suas rotinas de inicialização.
É certo que isso está fora do escopo da linguagem C. No entanto, é relevante no contexto de um vinculador ao lidar com bibliotecas compartilhadas.
C não tem o fiasco da ordem de inicialização estática. Em C89, a regra era:
Portanto, uma variável estática com tipo escalar só poderia ser inicializada com uma única expressão constante. Se o tipo da variável for um array com um tipo de elemento escalar, então cada inicializador precisaria ser uma expressão constante e assim por diante. Como uma expressão constante não pode produzir efeitos colaterais nem depender de efeitos colaterais produzidos por qualquer outra avaliação, alterar a ordem de avaliação das expressões constantes não afeta o resultado. Além disso, o compilador pode simplesmente emitir dados já inicializados (ou seja, avaliar essas expressões constantes em tempo de compilação), portanto, quando o programa for iniciado, não haverá inicialização estática a ser feita.
As únicas expressões não constantes que podem ser avaliadas antes
main
são aquelas invocadas no tempo de execução C. É por isso que osFILE
objetos apontados porstdin
,stdout
estderr
já estão disponíveis para uso pela primeira instrução demain
, por exemplo. O C padrão não permite que os usuários registrem seu próprio código de inicialização para ser executado antesmain
- embora o GCC forneça uma extensão chamada__constructor__
(presumivelmente nomeada após o recurso C++) que você pode usar para recriar o fiasco da ordem de inicialização estática em C, se desejar. .Stroustrup escreveu em The Design and Evolution of C++ que seu objetivo era tornar os tipos definidos pelo usuário utilizáveis onde quer que estivessem os tipos integrados. Isso significava que o C++ tinha que permitir variáveis globais do tipo classe, o que significa que seus construtores seriam chamados durante a inicialização do programa. Como o C++ antigo não tinha
constexpr
funções, essas chamadas de construtor nunca poderiam ser expressões constantes. E assim nasceu o fiasco da ordem de inicialização estática.Durante o processo de padronização do C++, a questão da ordem de execução da inicialização estática foi um tema controverso. Acho que a maioria das pessoas concordaria que o idealsituação seria que cada variável estática fosse inicializada antes de seu uso. Infelizmente, isso requer tecnologia de linker que não existia naquela época (e provavelmente ainda não existe?). A inicialização de uma variável estática pode envolver chamadas de função, e essas funções podem ser definidas em outra TU, o que significa que você precisaria realizar uma análise de todo o programa para classificar topologicamente com sucesso as variáveis estáticas em ordem de dependência. É importante notar que mesmo que o C++ pudesse ter sido projetado dessa maneira, ainda assim não teria evitado completamente os problemas de ordem de inicialização. Imagine se você tivesse alguma biblioteca onde uma pré-condição da
use
função fosse que elainit
tivesse sido chamada em algum momento no passado. Então, se você tiver uma variável estática cujo inicializador chamainit
e outro cujo inicializador chamause
, então há uma dependência de ordem que o compilador não consegue ver.Em última análise, as garantias limitadas de ordem de inicialização que obtivemos no C++ 98 foram as melhores que pudemos obter nessas circunstâncias. Com o benefício de uma retrospectiva ilimitada, talvez alguém pudesse ter protestado que o padrão não estaria completo sem
constexpr
funções (e que as variáveis estáticas deveriam ser obrigadas a ter apenas inicialização constante).