Qual é o tempo de vida de uma variável estática em uma função C ++?

373

Se uma variável é declarada como staticno escopo de uma função, ela é inicializada apenas uma vez e mantém seu valor entre as chamadas de função. O que exatamente é a sua vida? Quando seu construtor e destruidor é chamado?

void foo() 
{ 
    static string plonk = "When will I die?";
}
Motti
fonte

Respostas:

257

A vida útil das staticvariáveis de função começa na primeira vez que [0] o fluxo do programa encontra a declaração e termina na finalização do programa. Isso significa que o tempo de execução deve executar alguma manutenção contábil para destruí-lo somente se ele foi realmente construído.

Além disso, como o padrão diz que os destruidores de objetos estáticos devem ser executados na ordem inversa da conclusão de sua construção [1] , e a ordem de construção pode depender da execução específica do programa, a ordem de construção deve ser levada em consideração .

Exemplo

struct emitter {
    string str;
    emitter(const string& s) : str(s) { cout << "Created " << str << endl; }
    ~emitter() { cout << "Destroyed " << str << endl; }
};

void foo(bool skip_first) 
{
    if (!skip_first)
        static emitter a("in if");
    static emitter b("in foo");
}

int main(int argc, char*[])
{
    foo(argc != 2);
    if (argc == 3)
        foo(false);
}

Resultado:

C:> sample.exe
criado em foo
Destruído em foo

C:> sample.exe 1
Criado em se
Criado em foo
Destruído em foo
Destruído em se

C:> sample.exe 1 2
Criado em foo
Criado em se
Destruído em se
Destruído em foo

[0]Como o C ++ 98 [2] não tem referência a vários threads, como isso se comportará em um ambiente com vários threads não é especificado e pode ser problemático, como Roddy menciona.

[1] Seção C ++ 98 3.6.3.1 [basic.start.term]

[2]No C ++ 11, as estáticas são inicializadas de maneira segura para threads, isso também é conhecido como Magic Statics .

Motti
fonte
2
Para tipos simples sem efeitos colaterais c'tor / d'tor, é uma otimização direta inicializá-los da mesma maneira que tipos simples globais. Isso evita os problemas de ramificação, bandeira e ordem de destruição. Isso não quer dizer que a vida deles seja diferente.
John McFarlane
11
Se a função puder ser chamada por vários threads, isso significa que você precisa garantir que as declarações estáticas devem ser protegidas por um mutex no C ++ 98 ??
Allyourcode
11
"destruidores" de objetos globais devem ser executados na ordem inversa da conclusão de sua construção "não se aplica aqui, porque esses objetos não são globais. Fim destruição de locais com estático ou duração de armazenamento thread é consideravelmente mais complicado do que LIFO puro, ver secção 3.6.3[basic.start.term]
Ben Voigt
2
A frase "no encerramento do programa" não está estritamente correta. E as estatísticas nas dlls do Windows que são carregadas e descarregadas dinamicamente? Obviamente, o padrão C ++ não lida com assemblies (seria bom se o fizesse), mas um esclarecimento sobre exatamente o que o padrão diz aqui seria bom. Se a frase "na finalização do programa" fosse incluída, tecnicamente, qualquer implementação do C ++ com conjuntos descarregados dinamicamente não seria conforme.
Roger Sanders
2
@ Motti Não acredito que o padrão permita explicitamente bibliotecas dinâmicas, mas até agora também não acreditava que houvesse algo específico no padrão que estivesse em desacordo com sua implementação. Obviamente, falando estritamente a linguagem aqui não afirma que objetos estáticos não podem ser destruídos anteriormente por outros meios, apenas que eles devem ser destruídos ao retornar do main ou ao chamar std :: exit. Uma linha tênue, embora eu ache.
Roger Sanders
125

Motti está certo sobre o pedido, mas há outras coisas a considerar:

Os compiladores geralmente usam uma variável de sinalizador oculta para indicar se as estatísticas locais já foram inicializadas e esse sinalizador é verificado em todas as entradas da função. Obviamente, esse é um pequeno impacto no desempenho, mas o que é mais preocupante é que esse sinalizador não é garantido como seguro para threads.

Se você tem uma estática local, como acima, e fooé chamado de vários encadeamentos, pode haver condições de corrida que plonkpodem ser inicializadas incorretamente ou mesmo várias vezes. Além disso, nesse caso, plonkpode ser destruído por um thread diferente daquele que o construiu.

Apesar do que diz o padrão, eu ficaria muito cauteloso com a ordem real da destruição estática local, porque é possível que você involuntariamente confie em uma estática ainda válida após ser destruída, e isso é realmente difícil de rastrear.

Roddy
fonte
68
O C ++ 0x requer que a inicialização estática seja segura para threads. Portanto, tenha cuidado, mas as coisas só melhorarão.
Deft_code 7/10/10
Problemas de ordem de destruição podem ser evitados com um pouco de política. objetos estáticos / globais (singletons, etc) não devem acessar outros objetos estáticos em seus corpos de métodos. Eles devem ser acessados ​​apenas em construtores onde uma referência / ponteiro pode ser armazenada para acesso posterior nos métodos. Isso não é perfeito, mas deve corrigir 99 dos casos e os casos que ele não captura são obviamente suspeitos e devem ser detectados em uma revisão de código. Isso ainda não é uma solução perfeita como a política não pode ser imposta na língua
deft_code
Sou meio noob, mas por que essa política não pode ser aplicada no idioma?
precisa saber é o seguinte
9
Desde o C ++ 11, isso não é mais um problema. A resposta de Motti é atualizada de acordo com isso.
Nilanjan Basu
10

As explicações existentes não são realmente completas sem a regra real da Norma, encontrada em 6.7:

A inicialização zero de todas as variáveis ​​de escopo de bloco com duração de armazenamento estático ou duração de armazenamento de encadeamento é executada antes de qualquer outra inicialização. A inicialização constante de uma entidade de escopo de bloco com duração de armazenamento estático, se aplicável, é executada antes do primeiro bloco ser inserido. É permitida uma implementação para executar a inicialização antecipada de outras variáveis ​​do escopo de bloco com duração de armazenamento estático ou de encadeamento nas mesmas condições em que uma implementação pode inicializar estaticamente uma variável com duração de armazenamento estático ou de encadeamento no escopo do espaço para nome. Caso contrário, essa variável é inicializada na primeira vez em que o controle passa por sua declaração; essa variável é considerada inicializada após a conclusão de sua inicialização. Se a inicialização sair lançando uma exceção, a inicialização não está completa, portanto, será tentada novamente na próxima vez que o controle entrar na declaração. Se o controle digitar a declaração simultaneamente enquanto a variável estiver sendo inicializada, a execução simultânea aguardará a conclusão da inicialização. Se o controle entrar novamente na declaração recursivamente enquanto a variável estiver sendo inicializada, o comportamento será indefinido.

Ben Voigt
fonte
8

FWIW, o Codegear C ++ Builder não destrói na ordem esperada, de acordo com o padrão.

C:\> sample.exe 1 2
Created in foo
Created in if
Destroyed in foo
Destroyed in if

... que é outra razão para não confiar na ordem de destruição!

Roddy
fonte
57
Não é um bom argumento. Eu diria que este é mais um argumento para não usar este compilador.
Martin Iorque
26
Hmm. Se você estiver interessado em produzir código portátil do mundo real, em vez de apenas teoricamente portátil, acho útil saber quais áreas da linguagem podem causar problemas. Eu ficaria surpreso se o C ++ Builder fosse único em não lidar com isso.
Roddy
17
Eu concordaria, exceto que eu a chamaria de "quais compiladores causam problemas e em quais áreas do idioma eles fazem isso";
Steve Jessop
0

As variáveis ​​estáticas são ativadas quando a execução do programa é iniciada e permanecem disponíveis até o término da execução do programa.

As variáveis ​​estáticas são criadas no segmento de dados da memória .

Chandra Shekhar
fonte
isso não é verdade para variáveis ​​no escopo da função
awerries