2015-10-14 77 views
3

最近我的公司已經開始從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; 
} 

當然,在我們的代碼有很多其他的事情怎麼回事,但是這個代碼顯示出,我們在主項目看到了同樣的行爲。在VS2010中,這會構建,運行並終止「正常」。在VS2015中,它陷入僵局。

我也嘗試過在ideone.com中使用各種版本的C++,所有這些都重現了死鎖行爲。這對我來說是合理的,這不起作用(也不應該起作用),因爲對象不應該依賴於它自己。

我更加好奇的是爲什麼在VS2010中「工作」?標準對靜態變量初始化有什麼要說的?這只是一個VS2010(可能更早)的編譯器錯誤?

回答

6

的標準說:

如果控制進入聲明同時而[靜態或線程存儲時限塊範圍可變]正在初始化,併發執行應等待初始化的完成。如果控制在初始化變量時遞歸地重新輸入聲明,則行爲是未定義的。

([stmt.dcl]/4)

作出C++ 11的改變是局部靜態變量的初始化需要爲線程安全的。標準不允許遞歸,它會在期間再次通過聲明進行初始化,並且結果的UB在您的情況下顯示爲死鎖---這非常合理,因爲第二遍聲明會一直等待第一個聲明一個來完成。

現在,這在C++ 03中也是未定義的行爲,但在C++ 03實現中,初始化並不要求是線程安全的,因此可能發生的情況是:在第一次傳遞聲明,標誌被設置,然後調用構造函數;第二遍看到該標誌,假定該變量已經初始化,然後返回對其的引用。然後初始化完成。

顯然,您應該重寫您的代碼,以避免此遞歸初始化。

+0

更多信息:即使沒有這個具體的規定,如果它沒有可觀察到的行爲,它仍然是UB的一部分,關於一個線程可假設終止 –

+1

非常好,這正是我想知道的。不幸的是,雖然我同意你的評估,即代碼應該被重寫,但是它帶有非常高的開發成本,我們無法承受(實際的代碼自然會更復雜)。現有的代碼庫有很多問題,比這更深遠。我們正在逐漸轉向新的架構,所以我們不會永遠陷入這樣的困境,但現在我們仍然堅持下去。 – Kyle

+1

此外,就VS2015而言,有一種解決方法不涉及重寫有問題的代碼。使用/ Zc:threadSafeInit-強制VS2015編譯器放棄線程安全合規性和功能,與2010年的運行方式類似。顯然,這是一個可怕的黑客攻擊。 – Kyle