静态方法与静态变量的重入

Kyl*_*yle 4 c++ visual-c++

最近,我的公司已经开始从Visual Studio 2010升级到Visual Studio 2015.我们目前遇到的问题显然似乎源于编译器行为的变化.我们可以构建并运行我们的解决方案,但它似乎陷入僵局(它似乎只是空闲:CPU使用率几乎为0).

通过调试器,我们发现了一个问题,即单例对象在初始化期间依赖于自身.这是一个非常简化的版本:

#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;
}
Run Code Online (Sandbox Code Playgroud)

当然,在我们的代码中还有很多其他事情正在发生,但是这段代码表现出我们在主项目中看到的相同行为.在VS2010中,它"正常"构建,运行和终止.在VS2015中它陷入僵局.

我也在ideone.com中尝试了各种版本的C++,并且所有这些都重现了死锁行为.我觉得这不起作用(也不应该起作用),因为对象不应该依赖于它自己.

我更加好奇的是为什么这在VS2010中"起作用"?标准对静态变量初始化有什么看法?这只是一个VS2010(可能更早)的编译器错误吗?

Bri*_*ian 7

标准说:

如果控件在初始化[具有静态或线程存储持续时间的块作用域变量]的同时进入声明,则并发执行应等待初始化完成.如果控件在初始化变量时以递归方式重新输入声明,则行为未定义.

([stmt.dcl]/4)

在C++ 11中进行的更改是本地静态变量的初始化需要是线程安全的.标准不允许初始化期间再次通过声明的递归,并且结果的UB在你的情况下表现为死锁 - 这是完全合理的,因为第二次通过声明正在等待第一个到第一个完成.

现在,这也是C++ 03中未定义的行为,但在C++ 03实现中,初始化不需要是线程安全的,所以可能发生的是:在第一次通过声明时,a设置标志,然后调用构造函数; 第二遍看到标志,假定变量已经初始化,然后返回对它的引用.然后初始化完成.

显然,您应该重写代码以避免这种递归初始化.