局部靜態變量只能初始化一次是怎么實現?


靜態變量可以分為全局靜態變量,和局部靜態變量,先來說說全局的吧。全局靜態變量和全局變量的區別並不大,只是全局靜態變量只能在當前文件中使用,而在反匯編中二者並無區別,只可以在當前文件中使用,不過是編譯器做出的限制。局部靜態變量,會有些特殊,它不會隨着作用域結束而消失,在未進入作用於之前就已經存在。局部靜態變量和全局變量都保存在二進制文件的數據區,而在代碼中的限制,不過是編譯器限制而已。

那么當某個函數頻繁調用局部靜態變量時,C++的語法規定局部靜態變量只能初始化一次,那么編譯器是怎么做到的呢。

來看代碼:

void ShowStatic(int nNum)
{
    static int gnNumber = nNum;
    printf("%d\n", gnNumber);
}

void main()
{
    ShowStatic(99);
}

匯編代碼:

00E51738  mov         eax,dword ptr ds:[00E5A148h] 
00E5173D  and         eax,1 
00E51740  jne         ShowStatic+47h (0E51757h) 
00E51742  mov         eax,dword ptr ds:[00E5A148h] 
00E51747  or          eax,1 
00E5174A  mov         dword ptr ds:[00E5A148h],eax 
00E5174F  mov         eax,dword ptr [nNum] 
00E51752  mov         dword ptr [gnNumber (0E5A144h)],eax

可以看出,靜態變量的賦值比普通變量賦值多了很多步驟,我們來分析下。

首先在地址00E5A148h中保存了局部靜態變量的標志,這個標志占1個字節。通過位運算,將標志中的一位數據置1,來判斷局部靜態變量是否初始化過。而這個標志可以同時保存8個局部靜態變量的初始狀態。

通常這個標志出現在最先定義的局部靜態變量的附近,例如此例局部變量應出現在 00E5A144h 或 00E5A14Ch中。當同一個作用域內超過了8個靜態局部變量,下一個標記將會除了現在第9個定義的局部靜態變量地址的附近。現在再來看上面的匯編代碼就很清晰了:

00E51738  mov         eax,dword ptr ds:[00E5A148h] 
00E5173D  and         eax,1 
00E51740  jne         ShowStatic+47h (0E51757h)

斷是否已經初始化,如果已經初始化就跳轉到printf輸出內容,否則不跳轉繼續執行。

00E51742  mov         eax,dword ptr ds:[00E5A148h] 
00E51747  or          eax,1 
00E5174A  mov         dword ptr ds:[00E5A148h],eax 
00E5174F  mov         eax,dword ptr [nNum] 
00E51752  mov         dword ptr [gnNumber (0E5A144h)],eax

未初始化的情況,將標志位置位為1,並初始化gnNumber。

還有這樣一個問題,編譯器讓其他作用域對局部靜態變量不可見,這是怎么做到的?在編譯的過程中,編譯器會對變量,函數等進行名稱粉碎,也就是靜態變量被重新命名了。

我們觀察下編譯期結束后生成的obj文件,在這個文件中搜索靜態變量的名字(本文用HxD軟件打開obj文件),搜索結果如下圖:

 

 

名稱粉碎后,在原有名稱中加加入了一些額外信息,入作用域,類型等。像C++重載也是名稱粉碎的原理。

下面的匯編是在C++11中編譯的結果,顯然和上文的有些差距:
static int gnNumber = nNum;
00C11818  mov         eax,dword ptr [_tls_index (0C1B190h)] 
00C1181D  mov         ecx,dword ptr fs:[2Ch] 
00C11824  mov         edx,dword ptr [ecx+eax*4] 
00C11827  mov         eax,dword ptr ds:[00C1B150h] 
00C1182C  cmp         eax,dword ptr [edx+104h] 
00C11832  jle         ShowStatic+6Fh (0C1185Fh) 
00C11834  push        0C1B150h 
00C11839  call        __Init_thread_header (0C110DCh) 
00C1183E  add         esp,4 
00C11841  cmp         dword ptr ds:[0C1B150h],0FFFFFFFFh 
00C11848  jne         ShowStatic+6Fh (0C1185Fh) 
00C1184A  mov         eax,dword ptr [nNum] 
00C1184D  mov         dword ptr [gnNumber (0C1B14Ch)],eax 
00C11852  push        0C1B150h 
00C11857  call        __Init_thread_footer (0C11177h) 
00C1185C  add         esp,4

前三行代碼:

00C11818  mov         eax,dword ptr [_tls_index (0C1B190h)] 
00C1181D  mov         ecx,dword ptr fs:[2Ch] 
00C11824  mov         edx,dword ptr [ecx+eax*4]

TLS?怎么還多了兩個函數?__Init_thread_header_Init_thread_footer。這兩個函數是用來保證局部的靜態對象的初始化線程安全。但局部變量的互斥還是老樣子,只不過被封裝進上述的兩個函數之中了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM