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

StackOverflow https://stackoverflow.com/questions/55510

  •  09-06-2019
  •  | 
  •  

Pergunta

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

int globalgarbage;
unsigned int anumber = 42;

Mas e os estáticos definidos dentro de uma função?

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

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

Foi útil?

Solução

Fiquei curioso sobre isso, então escrevi o seguinte programa de teste e o compilei com 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 o 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á 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

Outras dicas

Algum palavreado relevante do C++ Standard:

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 (básico.stc.static) deve ser inicializado com zero (dcl.init) antes que qualquer outra inicialização ocorra.Objetos dos tipos de pod (tipos básicos) com duração de armazenamento estático inicializado com expressões constantes (expr.const) deve ser inicializado antes que qualquer inicialização dinâmica ocorra.Os objetos do escopo do espaço para nome com duração de armazenamento estático definidos 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.[Observação: 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 (básico.stc.static) é realizado antes que qualquer outra inicialização ocorra.Um objeto local do tipo de pod (tipos básicos) com a duração do armazenamento estático inicializado com expressões constantes é inicializado antes que seu bloco seja inserido pela primeira vez.Uma implementação pode realizar a inicialização precoce de outros objetos locais com duração de armazenamento estático nas mesmas condições que uma implementação é permitida para inicializar estaticamente um objeto com duração de armazenamento estático no escopo do espaço para nome (básico.start.init).Caso contrário, esse objeto será inicializado, a primeira vez que o controle passa por sua declaração;Esse objeto é considerado inicializado após a conclusão de sua inicialização.Se a inicialização sair lançando uma exceção, a inicialização não estará concluída; portanto, será tentada novamente na próxima vez que o controle entrará na declaração.Se o controle reentra a declaração (recursivamente) enquanto o objeto estiver 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 for construída.[Observação: básico.start.term descreve a ordem em que objetos locais com duração de armazenamento estático são destruídos.]

A memória para todas as variáveis ​​estáticas é alocada no carregamento do programa.Mas variáveis ​​estáticas locais são criadas e inicializadas na primeira vez 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 estarão localizadas na memória.

O compilador alocará variáveis ​​estáticas definidas em uma função foo no carregamento do programa, no entanto, o compilador também adicionará algumas instruções adicionais (código de máquina) à sua função foo para que na primeira vez que for invocado este código adicional inicialize a variável estática (por exemploinvocando o construtor, se aplicável).

@Adão:Essa injeção de código nos bastidores pelo compilador é a razão do resultado que você viu.

Tento testar novamente o código de Adam Pierce e adicionou mais dois casos:variável estática em classe e tipo de POD.Meu compilador é g++ 4.8.1, no sistema operacional Windows (MinGW-32).O resultado é que 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:construtor é chamado antes de entrar principal função (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 é do tipo POD, então ele também é inicializado antes de entrar principal função (1).Exemplo para tipo de POD: número interno estático = 10;

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

incluir <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 no ambiente Linux?

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 no nível da 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 aproveitar - 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++ (escopo global), objetos estáticos têm seus construtores chamados como parte da inicialização do programa, sob o controle da biblioteca de tempo de execução C.No Visual C++, pelo menos a ordem em que os objetos são inicializados pode ser controlada pelo init_seg pragma.

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

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top