Variáveis estáticas locais são tímidas
Caloni, 2018-02-20 computer blogUma dúvida muito comum dos programadores iniciantes em C/C++ diz respeito às variáveis static que são declaradas dentro de um escopo, como uma função. Sabemos que se ela fosse declarada global, fora de qualquer escopo, ela seria inicializada antes do main ser chamado, como diz este trecho de alguém que pesquisou a respeito:
"C++ Primer says. Each local static variable is initialized before the first time execution passes through the object's definition. Local statics are not destroyed when a function ends; they are destroyed when program terminates." - Someone that google it for but did not get it.
Mas no caso de variáveis static declaradas dentro de uma função isso não acontece, e ela pode ser inicializada a qualquer momento. Basta alguém chamar a função onde ela foi definida.
#include <iostream>
int func2()
{
std::cout << "Func2 called\n";
return 21;
}
int func()
{
static int st_x = func2();
return st_x * 2;
}
int main()
{
std::cout << "Passing by...\n";
std::cout << "Func returns " << func() << std::endl;
std::cout << "Exiting...\n";
}
c:\Projects\caloni\projects>cl /EHsc static_local_sample.cpp
Microsoft (R) C/C++ Optimizing Compiler Version 19.12.25827 for x86
Copyright (C) Microsoft Corporation. All rights reserved.
static_local_sample.cpp
Microsoft (R) Incremental Linker Version 14.12.25827.0
Copyright (C) Microsoft Corporation. All rights reserved.
/out:static_local_sample.exe
static_local_sample.obj
c:\Projects\caloni\projects>
c:\Projects\caloni\projects>static_local_sample.exe
Passing by...
Func2 called
Func returns 42
Exiting...
c:\Projects\caloni\projects>
Note que mesmo trocando static int para static const int a mesma coisa acontece. Apenas conseguimos forçar a inicialização antes do main quando há alguma variável global (static ou não) que chame a função.
#include <iostream>
int func2()
{
std::cout << "Func2 called\n";
return 21;
}
int func()
{
static int st_x = func2();
return st_x * 2;
}
static const int g_x = func();
int main()
{
std::cout << "Passing by...\n";
std::cout << "Func returns " << func() << std::endl;
std::cout << "Exiting...\n";
}
c:\Projects\caloni\projects>cl /EHsc static_local_sample.cpp
Microsoft (R) C/C++ Optimizing Compiler Version 19.12.25827 for x86
Copyright (C) Microsoft Corporation. All rights reserved.
static_local_sample.cpp
Microsoft (R) Incremental Linker Version 14.12.25827.0
Copyright (C) Microsoft Corporation. All rights reserved.
/out:static_local_sample.exe
static_local_sample.obj
c:\Projects\caloni\projects>static_local_sample.exe
Func2 called
Passing by...
Func returns 42
Exiting...
c:\Projects\caloni\projects>
O problema disso é que é possível que duas threads chamem func() "ao mesmo tempo", gerando uma dupla inicialização caso a implementação da libc não seja thread-safe. E a menos que o padrão especifique que essa inicialização deva ser thread safe, melhor fazer as coisas direito.
Mas, a título de curiosidade, é bom saber que no Visual Studio 2017 essa parte da libc já possui um mecanismo de proteção, como o sugestivo nome _tls_index já indica:
c:\Projects\caloni\projects>cl /EHsc /Zi static_local_sample.cpp Microsoft (R) C/C++ Optimizing Compiler Version 19.12.25827 for x86 Copyright (C) Microsoft Corporation. All rights reserved. static_local_sample.cpp Microsoft (R) Incremental Linker Version 14.12.25827.0 Copyright (C) Microsoft Corporation. All rights reserved. /out:static_local_sample.exe /debug static_local_sample.obj c:\Projects\caloni\projects>windbg static_local_sample.exe static_local_sample!func [static_local_sample.cpp @ 10]: 10 000fe410 push ebp 10 000fe411 mov ebp,esp 10 000fe413 push 0FFFFFFFFh 10 000fe415 push offset static_local_sample!wcschr+0x1cc7 (0018806c) 10 000fe41a mov eax,dword ptr fs:[00000000h] 10 000fe420 push eax 10 000fe421 mov eax,dword ptr [static_local_sample!__security_cookie (001a9080)] 10 000fe426 xor eax,ebp 10 000fe428 push eax 10 000fe429 lea eax,[ebp-0Ch] 10 000fe42c mov dword ptr fs:[00000000h],eax 11 000fe432 mov eax,dword ptr [static_local_sample!_tls_index (001ab51c)] ((( Thread Local Storage? ))) 11 000fe437 mov ecx,dword ptr fs:[2Ch] 11 000fe43e mov edx,dword ptr [ecx+eax*4] 11 000fe441 mov eax,dword ptr [static_local_sample!st_x+0x4 (001ab028)] 11 000fe446 cmp eax,dword ptr [edx+104h] 11 000fe44c jle static_local_sample!func+0x79 (000fe489) ((( compara para ver se chama inicialização ou não ))) static_local_sample!func+0x3e [static_local_sample.cpp @ 11]: 11 000fe44e push offset static_local_sample!st_x+0x4 (001ab028) 11 000fe453 call static_local_sample!ILT+2810(__Init_thread_header) (000f1aff) 11 000fe458 add esp,4 11 000fe45b cmp dword ptr [ static_local_sample!st_x+0x4 (001ab028)],0FFFFFFFFh 11 000fe462 jne static_local_sample!func+0x79 (000fe489) static_local_sample!func+0x54 [static_local_sample.cpp @ 11]: 11 000fe464 mov dword ptr [ebp-4],0 11 000fe46b call static_local_sample!ILT+3110(?func2YAHXZ) (000f1c2b) ((( note a chamada a func2 ))) 11 000fe470 mov dword ptr [static_local_sample!st_x (001ab024)],eax 11 000fe475 mov dword ptr [ebp-4],0FFFFFFFFh 11 000fe47c push offset static_local_sample!st_x+0x4 (001ab028) 11 000fe481 call static_local_sample!ILT+6165(__Init_thread_footer) (000f281a) 11 000fe486 add esp,4 static_local_sample!func+0x79 [static_local_sample.cpp @ 12]: 12 000fe489 mov eax,dword ptr [static_local_sample!st_x (001ab024)] ((( a partir da segunda chamada tudo começa aqui ))) 12 000fe48e shl eax,1 13 000fe490 mov ecx,dword ptr [ebp-0Ch] 13 000fe493 mov dword ptr fs:[0],ecx 13 000fe49a pop ecx 13 000fe49b mov esp,ebp 13 000fe49d pop ebp 13 000fe49e ret