靜態變量可以分為全局靜態變量,和局部靜態變量,先來說說全局的吧。全局靜態變量和全局變量的區別並不大,只是全局靜態變量只能在當前文件中使用,而在反匯編中二者並無區別,只可以在當前文件中使用,不過是編譯器做出的限制。局部靜態變量,會有些特殊,它不會隨着作用域結束而消失,在未進入作用於之前就已經存在。局部靜態變量和全局變量都保存在二進制文件的數據區,而在代碼中的限制,不過是編譯器限制而已。
來看代碼:
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++重載也是名稱粉碎的原理。
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。
這兩個函數是用來保證局部的靜態對象的初始化線程安全。但局部變量的互斥還是老樣子,只不過被封裝進上述的兩個函數之中了。