搜尋

首頁  >  問答  >  主體

C++局部静态变量在什么时候分配内存和初始化?

class A
{
    public:
        A() 
        {
            sleep(10); // 故意让初始化过程放慢
            m_num = 1;
        };
        ~A() {};
        
        void print(int index) { printf("[%d] - %d", index, m_num); }
        
    private:
        int m_num;
}

void func(int index)
{
    static A a; // 静态局部变量,默认构造
    
    // A b;
    //static A a = b; // 静态局部变量,拷贝构造
    a.print(index);
}

int main()
{
    // 三个线程同时执行
    boost::thread trd1(boost::bind(&func, 1));
    boost::thread trd2(boost::bind(&func, 2));
    boost::thread trd3(boost::bind(&func, 3));
    
    sleep(1000);
    return 0;
}

请问,这个类对象局部变量是在什么时候分配内存和初始化的?拷贝构造的时候呢?
网上都说是在程序主函数执行前的静态初始化过程中分配内存并初始化的,但实际测试,当有3个线程同时执行func操作时,会有m_num = 0的输出,证明至少A对象的初始化过程没有完成。

如果是在主函数执行前的静态初始化过程中分配内存和初始化,那么在func中的定义过程貌似只是个赋值的过程?
或者说在静态初始化过程中分配内存,在第一次定义的地方初始化?这样多线程的情况下就会有个竞争初始化的问题?

突然想到了这个问题,求解答,谢谢。

伊谢尔伦伊谢尔伦2807 天前880

全部回覆(3)我來回復

  • 怪我咯

    怪我咯2017-04-17 13:15:52

    謝謝上面兩位同學的回答,結合自己的查詢和測試,整理了下最終的結果,在這裡貼下,如果發現有不對的地方,還請各位指正,謝謝。

    靜態變數的記憶體分配與初始化

    對於C語言的全域和靜態變量,不管是否被初始化,其記憶體空間都是全域的;如果初始化,那麼初始化發生在任何程式碼執行之前,屬於編譯期初始化。由於內建變數無須資源釋放操作,僅需要回收記憶體空間,因此程式結束後全域記憶體空間被一起回收,不存在變數依賴問題,沒有任何程式碼會再被執行!

    C++引入了對象,這給全域變數的管理帶領新的麻煩。 C++的物件必須有建構函式生成,並且最終執行析構操作。由於建構和析構並非分配記憶體那麼簡單,可以說相當複雜,因此何時執行全域或靜態物件(C++)的建構和析構呢?這需要執行相關程式碼,無法在編譯期間完成,因此C++標準規定:全域或靜態物件當且僅當物件首次用到時才進行構造,並透過atexit()來管理物件的生命期,在程式結束之後(如呼叫exit,main),以FILO順序呼叫對應的析構操作!

    總結:

    全域變數、檔案域的靜態變數和類別的靜態成員變數在main執行之前的靜態初始化過程中分配記憶體並初始化;局部靜態變數(一般為函數內的靜態變數)在第一次使用時分配記憶體並初始化。這裡的變數包含內建資料類型和自訂類型的物件。

    靜態變數初始化的執行緒安全性說明

    非局部靜態變數一般在main執行之前的靜態初始化過程中分配記憶體並初始化,可以認為是線程安全的;

    局部靜態變數在編譯時,編譯器的實作一般是在初始化語句之前設定一個局部靜態變數的標識來判斷是否已經初始化,運行的時候每次進行判斷,如果需要初始化則執行初始化操作,否則不執行。這個過程本身不是線程安全的。

    C++11標準針規定了局部靜態變數初始化需要確保執行緒安全,具體說明如下:
    If control enters the declaration concurrently while the variable is being initialized, the concurrent execution shall wait for completion of the initialized, the concurrent execution shall wait for completion of the initialization

    新的編譯器大多對C++11的標準支持,因此也保證了這一點,但是C++03標準之前並無此說明,所以很多舊版的編譯器並不能完全支援。

    註:VS2008 測試多執行緒的條件下雖然只有一個執行緒執行一次初始化,但非初始化的執行緒並不會等待初始化結束,而是立即傳回未正確初始化的靜態物件。

    針對局部靜態變數初始化的執行緒安全性,g++編譯器的實作相當於使用了一個全域鎖定來控制一個局部靜態變數的標識(標識用來判定是否已經初始化)。詳情參考:http://www.cnblogs.com/xuxm2007/p/4652944.html

    使用相關:

    以前的標準都沒有規定局部靜態變數的初始化在並發模式下是否安全,許多舊版的編譯器並沒有處理它的並發安全問題。因此在不支援C++11標準的編譯環境下,多執行緒程式最好不要使用需要明顯初始化的局部靜態變數(物件),如果需要使用(例如單例模式中),則可以考慮使用一個全域鎖或靜態成員變數鎖,最好不要使用局部靜態變數鎖,因為其本身有一個建構的問題,多個執行緒取得實例的時候,可能會出現一個執行緒在進行鎖物件構造,另一個執行緒則避開了構造,在鎖對象還沒有完全構造完成的情況下,就lock了,這個時候的行為能不能成功鎖定取決於鎖的實現了,雖然一般的實現不會出現問題但終歸不是很嚴謹。

    回覆
    0
  • 天蓬老师

    天蓬老师2017-04-17 13:15:52

    分配內存在程式啟動時。
    建構函式呼叫在第一次函式被呼叫時。
    c++98 不規定local static變數的初始化的執行緒安全性。事實上編譯器是創建了一個隱藏的變數 標識 static變數有沒有初始化。每次呼叫函數的時候檢查隱藏變數有否改變。
    c++11 規定了static變數的初始化的線程安全性。
    請參考 http://stackoverflow.com/questions/246564/what-is-the-lifetime-of-a-static-variable-in-a-c-function

    回覆
    0
  • 天蓬老师

    天蓬老师2017-04-17 13:15:52

    你這個是函數內靜態變量,初始化在第一次執行函數的時候初始化。如果放到func外面,就在main函式執行前初始化。你這個為啥會出現0,因為初始化只初始化一次,而你相當於在3個線程裡面呼叫只初始化一次的方法,這個是線程不安全的,初始化的時候有個標記,標記A已經初始化,如果初始化之後就不會再呼叫初始化。而你用了sleep,所以會有0出現。即便不sleep,也有可能出現0的情況,因為執行緒不安全。 3個執行緒都執行這樣的邏輯:
    a是否已經初始化?
    沒有初始化就設定初始化標記呼叫建構函式初始化

    有可能3個執行緒都呼叫初始化。也有可能只有1個初始化,其他執行緒執行的時候,雖然看到初始化標記,但是構造函數並沒完成,尤其是你sleep的時候,更可能發生。

    回覆
    0
  • 取消回覆