Quando as variáveis ​​estáticas de nível de função são alocadas / inicializadas?

89

Estou bastante confiante de que as variáveis ​​declaradas globalmente são alocadas (e inicializadas, se aplicável) na hora de início do programa.

int globalgarbage;
unsigned int anumber = 42;

Mas e quanto aos estáticos definidos em uma função?

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

Quando é o espaço globalishalocado? Estou adivinhando quando o programa começa. Mas ele também é inicializado? Ou é inicializado quando doSomething()é chamado pela primeira vez?

Owen
fonte

Respostas:

91

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

include <iostream>
#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;
};

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 os que eu esperava. O construtor do objeto estático não foi chamado até a primeira vez que a função foi chamada. Aqui está o resultado:

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
Adam Pierce
fonte
29
Como esclarecimento: a variável estática é inicializada na primeira vez que a execução atinge sua declaração, não quando a função que o contém é chamada. Se você tiver apenas um static no início da função (por exemplo, em seu exemplo), eles são iguais, mas não necessariamente: por exemplo, se você tiver 'if (...) {static MyClass x; ...} ', então' x 'não será inicializado em ALL durante a primeira execução dessa função no caso em que a condição da instrução if for avaliada como falsa.
EvanED
4
Mas isso não leva a uma sobrecarga de tempo de execução, já que cada vez que a variável estática é usada, o programa tem que verificar se ela foi usada anteriormente, caso contrário, ela deve ser inicializada? Nesse caso, isso é meio ruim.
HelloGoodbye
ilustração perfeita
Des1gnWizard
@veio: Sim, a inicialização é segura para threads. Veja essa pergunta para mais detalhes: stackoverflow.com/questions/23829389/…
Rémi
2
@HelloGoodbye: sim, isso leva a uma sobrecarga de tempo de execução. Veja também essa pergunta: stackoverflow.com/questions/23829389/…
Rémi
53

Alguns verbos relevantes do padrão C ++:

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

1

O armazenamento para objetos com duração de armazenamento estático ( basic.stc.static ) deve ser inicializado em zero ( dcl.init ) antes que qualquer outra inicialização ocorra. Objetos do tipo POD ( basic.types ) com duração de armazenamento estático inicializados com expressões constantes ( expr.const ) devem obrigatoriamente ser inicializados antes que qualquer inicialização dinâmica ocorra. Objetos de escopo de namespace com duração de armazenamento estático definido na mesma unidade de tradução e inicializados dinamicamente 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 inicialização de objetos 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 inicialização zero ( dcl.init ) de todos os objetos locais com duração de armazenamento estático ( basic.stc.static ) é realizada antes que qualquer outra inicialização ocorra. Um objeto 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 realizar a inicialização antecipada de outros objetos locais com duração de armazenamento estático sob as mesmas condições em que uma implementação tem permissão para inicializar estaticamente um objeto com duração de armazenamento estático no escopo do namespace ( basic.start.init) Caso contrário, esse objeto é inicializado na primeira vez que o controle passa por sua declaração; tal objeto é considerado inicializado após a conclusão de sua inicialização. Se a inicialização for encerrada lançando uma exceção, a inicialização não será concluída, portanto, ela 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 objeto está sendo inicializado, o comportamento é 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 objeto local com duração de armazenamento estático será executado se e somente se a variável foi construída. [Observação: basic.start.term descreve a ordem em que os objetos locais com duração de armazenamento estático são destruídos. ]

Jason Plank
fonte
Isso respondeu à minha pergunta e não se baseia em "evidências anedóticas" ao contrário da resposta aceita. Eu estava procurando especificamente por esta menção de exceções no construtor de objetos estáticos locais de função inicializados estaticamente:If the initialization exits by throwing an exception, the initialization is not complete, so it will be tried again the next time control enters the declaration.
Bensge
26

A memória para todas as variáveis ​​estáticas é alocada no carregamento do programa. Mas as variáveis ​​estáticas locais são criadas e inicializadas na primeira vez em que são usadas, não na inicialização do programa. Há algumas boas leituras sobre isso e estática em geral aqui . Em geral, acho que alguns desses problemas dependem da implementação, especialmente se você quiser saber onde essas coisas serão localizadas na memória.

Eugene
fonte
2
não é bem assim, os estáticos locais são alocados e inicializados com zero "no carregamento do programa" (entre aspas, porque isso também não está certo) e, em seguida, reinicializados na primeira vez que a função em que eles estão é inserida.
Mooing Duck
Parece que o link está quebrado 7 anos depois.
Steve
1
Sim, o link foi quebrado. Aqui está um arquivo: web.archive.org/web/20100328062506/http://www.acm.org/…
Eugene
10

O compilador irá alocar variável (s) estática (s) definida (s) em uma função foono carregamento do programa, no entanto, o compilador também adicionará algumas instruções adicionais (código de máquina) à sua função foopara que na primeira vez que for invocado, este código adicional inicialize a variável estática ( por exemplo, invocando o construtor, se aplicável).

@Adam: Esta injeção de código nos bastidores pelo compilador é a razão para o resultado que você viu.

Henk
fonte
5

Tento testar novamente o código de Adam Pierce e adicionei mais dois casos: variável estática na classe e tipo POD. Meu compilador é g ++ 4.8.1, no sistema operacional Windows (MinGW-32). O resultado é a variável estática na classe é tratada da mesma forma que a variável global. Seu construtor será chamado antes de entrar na função principal.

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

    1. Variável global e membro estático na classe : o construtor é chamado antes de entrar na função principal (1) .
    2. Variável estática local : o construtor só é chamado 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 função principal (1) . Exemplo para o tipo de POD: static int number = 10;

(1) : O estado correto deve ser: "antes que qualquer função da mesma unidade de tradução seja chamada". Porém, por simples, como no exemplo abaixo, então é a função principal .

inclui <iostream>

#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?

Thang Le
fonte
3

Variáveis ​​estáticas são alocadas dentro de um segmento de código - elas fazem parte da imagem executável e, portanto, são mapeadas já inicializadas.

Variáveis ​​estáticas dentro do escopo da função 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 com 0 (a menos que você especifique outra coisa) em vez de um valor indefinido.

Existem algumas outras facetas da inicialização que você pode tirar vantagem - por exemplo, segmentos compartilhados permitem que diferentes instâncias do seu executável em execução ao mesmo tempo acessem as mesmas variáveis ​​estáticas.

Em C ++ (com escopo global), objetos estáticos têm seus construtores chamados como parte da inicialização do programa, sob o controle da biblioteca C runtime. No Visual C ++, pelo menos a ordem em que os objetos são inicializados pode ser controlada pelo pragma init_seg .

Rob Walker
fonte
4
Esta pergunta é sobre estática com escopo de função. Pelo menos quando eles têm construtores não triviais, eles são inicializados na primeira entrada na função. Ou mais especificamente, quando essa linha é alcançada.
Adam Mitz,
Verdade - mas a questão fala sobre o espaço alocado para a variável e usa tipos de dados simples. O espaço ainda está alocado no segmento de código
Rob Walker,
Não vejo como o segmento de código vs. segmento de dados realmente importa aqui. Acho que precisamos de esclarecimentos do OP. Ele disse "e inicializou, se aplicável".
Adam Mitz,
5
variáveis ​​nunca são alocadas dentro do segmento de código; assim, eles não seriam graváveis.
botismarius
1
variáveis ​​estáticas têm espaço alocado no segmento de dados ou no segmento bss, dependendo se são inicializadas ou não.
EmptyData
3

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

Sim, ele é. 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 nas próximas chamadas, seu valor será retornado (por referência).

dmityugov
fonte