Note: The word almost is used because a global variable will be initialized at process start (i.e. its constructor will be called before entering main
) whereas the static variable inside a function will be initialized the first time the statement is executed.
Your question is wrong from the start:
ID generator with local static variable - thread-safe?
In C/C++, a variable that is static inside a function or inside a class/struct declaration behaves (almost) like a global variable, not a local stack-based one.
The following code:
int getUniqueID()
{
static int ID=0;
return ++ID;
}
Would be (almost) similar to the pseudo-code :
private_to_the_next_function int ID = 0 ;
int getUniqueID()
{
return ++ID;
}
with the pseudo-keyword private_to_the_next_function
making the variable invisible to all other functions but getUniqueId...
Here, static
is only hiding the variable, making its access from other functions impossible...
But even hidden, the variable ID remains global: Should getUniqueId be called by multiple threads, ID will be as thread safe as other global variables, that is, not thread-safe at all.
Edit : Lifetime of variables
After reading comments, I felt I was not clear enough with my answer. I am not using global/local notions for their scope signification, but for their lifetime signification:
A global will live as long as the process is running, and a local, which is allocated on the stack, will start its life when entering the scope/function, and will cease to exist the moment the scope/function is exited. This means a global will retain its value, while a local will not. This also means a global will be shared between threads, while a local will not.
Add to it the static
keyword, which has different meanings depending on the context (this is why using static
on global variables and on functions in C++ is deprecated in favour of anonymous namespaces, but I disgress).
When qualifying a local variable, this local ceases to behave like a local. It becomes a global hidden inside a function. So it behaves as if the value of a local variable was magically remembered between the function calls, but there's not magic: The variable is a global one, and will remain "alive" until the end of the program.
You can "see" this by logging the creation and destruction of an object declared static inside a function. The construction will happen when the declaration statement will be executed, and the destruction will happen at the end of the process:
bool isObjectToBeConstructed = false ;
int iteration = 0 ;
struct MyObject
{
MyObject() { std::cout << "*** MyObject::MyObject() ***" << std::endl ; }
~MyObject() { std::cout << "*** MyObject::~MyObject() ***" << std::endl ; }
};
void myFunction()
{
std::cout << " myFunction() : begin with iteration " << iteration << std::endl ;
if(iteration < 3)
{
++iteration ;
myFunction() ;
--iteration ;
}
else if(isObjectToBeConstructed)
{
static MyObject myObject ;
}
std::cout << " myFunction() : end with iteration " << iteration << std::endl ;
}
int main(int argc, char* argv[])
{
if(argc > 1)
{
std::cout << "main() : begin WITH static object construction." << std::endl ;
isObjectToBeConstructed = true ;
}
else
{
std::cout << "main() : begin WITHOUT static object construction." << std::endl ;
isObjectToBeConstructed = false ;
}
myFunction() ;
std::cout << "main() : end." << std::endl ;
return 0 ;
}
If you launch the executable without parameters, the execution will never go through the static object declaration, and so, it will never be constructed nor destructed, as shown by the logs:
main() : begin WITHOUT static object construction.
myFunction() : begin with iteration 0
myFunction() : begin with iteration 1
myFunction() : begin with iteration 2
myFunction() : begin with iteration 3
myFunction() : end with iteration 3
myFunction() : end with iteration 2
myFunction() : end with iteration 1
myFunction() : end with iteration 0
main() : end.
But if you launch it with a parameter, then the object will be constructed at the third recursive call of myFunction, and destroyed only at the end of the process, as seen by the logs:
main() : begin WITH static object construction.
myFunction() : begin with iteration 0
myFunction() : begin with iteration 1
myFunction() : begin with iteration 2
myFunction() : begin with iteration 3
*** MyObject::MyObject() ***
myFunction() : end with iteration 3
myFunction() : end with iteration 2
myFunction() : end with iteration 1
myFunction() : end with iteration 0
main() : end.
*** MyObject::~MyObject() ***
Now, if you play with the same code, but calling myFunction through multiple threads, you'll have race conditions on the constructor of myObject. And if you call this myObject methods or use this myObject variables in myFunction called by multiple threads, you'll have race conditions, too.
Thus, the static local variable myObject is simply a global object hidden inside a function.