Quando as variables ​​estáticas no nível da function são alocadas / inicializadas?

Estou bastante confiante de que as variables ​​declaradas globalmente são alocadas (e inicializadas, se aplicável) no horário de início do programa.

int globalgarbage; unsigned int anumber = 42; 

Mas e os estáticos definidos dentro de uma function?

 void doSomething() { static bool globalish = true; // ... } 

Quando o espaço para globalish alocado? Eu estou supondo quando o programa é iniciado. Mas será inicializado também? Ou é inicializado quando doSomething() é chamado pela primeira vez?

Fiquei curioso sobre isso, então escrevi o seguinte programa de teste e o compilei com o g ++ versão 4.1.2.

 include  #include  using namespace std; class test { public: test(const char *name) : _name(name) { cout < < _name << " created" << endl; } ~test() { cout << _name << " destroyed" << endl; } string _name; }; test t("global variable"); void f() { static test t("static variable"); test t2("Local variable"); cout << "Function executed" << endl; } int main() { test t("local to main"); cout << "Program start" << endl; f(); cout << "Program end" << endl; return 0; } 

Os resultados não foram o que eu esperava. O construtor para o object estático não foi chamado até a primeira vez em que a function foi chamada. Aqui está a saída:

 global variable created local to main created Program start static variable created Local variable created Function executed Local variable destroyed Program end local to main destroyed static variable destroyed global variable destroyed 

Alguns verbos relevantes do C ++ Standard:

3.6.2 Inicialização de objects não locais [basic.start.init]

1

O armazenamento para objects com duração de armazenamento estático ( basic.stc.static ) deve ser inicializado com zero ( dcl.init ) antes que qualquer outra boot ocorra. Objetos de tipos POD ( basic.types ) com duração de armazenamento estático inicializado com expressões constantes ( expr.const ) devem ser inicializados antes que qualquer boot dinâmica ocorra. Objetos de escopo de namespace com duração de armazenamento estático definidos na mesma unidade de tradução e dinamicamente inicializados devem ser inicializados na ordem em que sua definição aparece na unidade de tradução. [Nota: dcl.init.aggr descreve a ordem em que os membros agregados são inicializados. A boot de objects estáticos locais é descrita em stmt.dcl . ]

[mais texto abaixo adicionando mais liberdades para escritores de compiladores]

6.7 Declaração de declaração [stmt.dcl]

4

A boot zero ( dcl.init ) de todos os objects locais com duração de armazenamento estático ( basic.stc.static ) é executada antes que qualquer outra boot ocorra. Um object local do tipo POD ( basic.types ) com duração de armazenamento estático inicializado com expressões constantes é inicializado antes de seu bloco ser inserido pela primeira vez. Uma implementação tem permissão para executar a boot antecipada de outros objects locais com duração de armazenamento estático sob as mesmas condições que uma implementação é permitida para inicializar estaticamente um object com duração de armazenamento estático no escopo do namespace ( basic.start.init ). Caso contrário, tal object é inicializado na primeira vez que o controle passa por sua declaração; esse object é considerado inicializado após a conclusão de sua boot. Se a boot sair lançando uma exceção, a boot não estará completa, portanto será tentada novamente na próxima vez que o controle entrar na declaração. Se o controle entrar novamente na declaração (recursivamente) enquanto o object estiver sendo inicializado, o comportamento será indefinido. [ Exemplo:

  int foo(int i) { static int s = foo(2*i); // recursive call - undefined return i+1; } 

exemplo final

5

O destruidor de um object local com duração de armazenamento estático será executado se e somente se a variável foi construída. [Nota: basic.start.term descreve a ordem na qual os objects locais com duração de armazenamento estático são destruídos. ]

A memory para todas as variables ​​estáticas é alocada no carregamento do programa. Mas as variables ​​estáticas locais são criadas e inicializadas na primeira vez que são usadas, não na boot do programa. Há uma boa leitura sobre isso, e estática em geral, aqui . Em geral, acho que algumas dessas questões dependem da implementação, especialmente se você quiser saber onde na memory essas coisas estarão localizadas.

O compilador alocará variables ​​estáticas definidas em uma function foo no carregamento do programa, no entanto o compilador também irá adicionar algumas instruções adicionais (código de máquina) à sua function foo para que a primeira vez que ele seja invocado esse código adicional inicialize o variável (por exemplo, invocando o construtor, se aplicável).

@Adam: Essa injeção de código por trás dos bastidores pelo compilador é a razão do resultado que você viu.

Eu tento testar novamente o código de Adam Pierce e adicionei mais dois casos: variável estática na class e tipo POD. Meu compilador é g ++ 4.8.1, no sistema operacional Windows (MinGW-32). Resultado é variável estática na class é tratada mesmo com a variável global. Seu construtor será chamado antes de entrar na function principal.

  • Conclusão (para g ++, ambiente Windows):

    1. Variável global e membro estático na class : o construtor é chamado antes de entrar na function principal (1) .
    2. Variável estática local : o construtor é chamado apenas quando a execução atinge sua declaração pela primeira vez.
    3. Se a variável estática local for do tipo POD , ela também será inicializada antes de entrar na function principal (1) . Exemplo para o tipo POD: static int number = 10;

(1) : O estado correto deve ser: “antes que qualquer function da mesma unidade de tradução seja chamada”. No entanto, para simples, como no exemplo abaixo, então é a function principal .

include

 #include < string> using namespace std; class test { public: test(const char *name) : _name(name) { cout < < _name << " created" << endl; } ~test() { cout << _name << " destroyed" << endl; } string _name; static test t; // static member }; test test::t("static in class"); test t("global variable"); void f() { static test t("static variable"); static int num = 10 ; // POD type, init before enter main function test t2("Local variable"); cout << "Function executed" << endl; } int main() { test t("local to main"); cout << "Program start" << endl; f(); cout << "Program end" << endl; return 0; } 

resultado:

 static in class created global variable created local to main created Program start static variable created Local variable created Function executed Local variable destroyed Program end local to main destroyed static variable destroyed global variable destroyed static in class destroyed 

Alguém testou em env Linux?

As variables ​​estáticas são alocadas dentro de um segmento de código – elas fazem parte da imagem executável e, portanto, são mapeadas no já inicializado.

Variáveis ​​estáticas dentro do escopo da function são tratadas da mesma forma, o escopo é puramente uma construção de nível de linguagem.

Por esse motivo, você tem a garantia de que uma variável estática será inicializada como 0 (a menos que você especifique outra coisa) em vez de um valor indefinido.

Existem outras facetas na boot das quais você pode tirar proveito – por exemplo, segmentos compartilhados permitem que instâncias diferentes de seu executável sejam executadas de uma só vez para acessar as mesmas variables ​​estáticas.

Em objects estáticos C ++ (com escopo global), seus construtores são chamados como parte da boot do programa, sob o controle da biblioteca de tempo de execução C. No Visual C ++, pelo menos, a ordem em que os objects são inicializados pode ser controlada pelo pragma init_seg .

Ou é inicializado quando doSomething () é chamado pela primeira vez?

Sim. Isso, entre outras coisas, permite inicializar estruturas de dados acessadas globalmente quando for apropriado, por exemplo, dentro de blocos try / catch. Por exemplo, em vez de

 int foo = init(); // bad if init() throws something int main() { try { ... } catch(...){ ... } } 

você pode escrever

 int& foo() { static int myfoo = init(); return myfoo; } 

e use-o dentro do bloco try / catch. Na primeira chamada, a variável será inicializada. Então, na primeira e na próxima chamada, seu valor será retornado (por referência).