Recently my company has begun the process of upgrading to Visual Studio 2015 from Visual Studio 2010. The problem we're currently running into apparently seems to stem from a change in the behavior of the compiler. We can build and run our solution, but it seems to deadlock (it seems to just idle: CPU usage is nearly 0).
Stepping through with the debugger we've discovered an issue where a singleton object depends on itself during initialization. Here's an extremely stripped down version:
#include <iostream>
using namespace std;
struct Singleton
{
Singleton( int n )
{
cout << "Singleton( " << n << " )" << endl;
cout << Singleton::Instance().mN << endl;
mN = n;
}
static Singleton& Instance()
{
static Singleton instance( 5 );
return instance;
}
int mN;
};
int main() {
cout << Singleton::Instance().mN << endl;
return 0;
}
Naturally in our code there's a lot of other things going on, but this code exhibits the same behavior that we're seeing in the main project. In VS2010, this builds, runs, and terminates "normally". In VS2015 it deadlocks.
I've also tried this in ideone.com with various versions of C++ and all of those reproduce the deadlocking behavior. It makes sense to me that this doesn't work (nor should it work), because the object shouldn't depend on itself.
What I'm more curious about is why did this "work" in VS2010? What does the standard have to say about static variable initialization? Was this just a VS2010 (and possibly earlier) compiler bug?
The standard says that:
If control enters the declaration concurrently while the [block-scope variable with static or thread storage duration] is being initialized, the concurrent execution shall wait for completion of the initialization. If control re-enters the declaration recursively while the variable is being initialized, the behavior is undefined.
([stmt.dcl]/4)
The change made in C++11 is that initialization of local static variables is required to be thread-safe. The standard disallows recursion that would pass through the declaration again during the initialization, and the UB that results is manifesting as deadlock in your case---which makes perfect sense, since the second pass through the declaration is waiting forever for the first one to complete.
Now, this was undefined behavior in C++03 as well, but in a C++03 implementation, the initialization is not required to be thread-safe, so what probably happens is this: on the first pass through the declaration, a flag is set and then the constructor is called; the second pass sees the flag, assumes the variable is already initialized, and then returns a reference to it. Then the initialization completes.
You should rewrite your code, obviously, to avoid this recursive initialization.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With