1. 程式人生 > >靜態變數初始化的時機

靜態變數初始化的時機

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

對於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 initialization

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

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

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

使用相關:

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