對《神奇的C語言》文中例子 5 代碼的分析討論

  在春節前,我曾經參與在《神奇的C語言》一文中的例子(5)的討論,但限於評論內容的有限,如今本文再次對這個問題單獨討論。(此問題原貌,詳見《神奇的C語言》,這裏我將原文中的代碼稍作輕微改動,並從新給出以下)html

  原問題給出以下代碼:數組

 

#include <stdio.h>
void func1(char a[])
{

  //這裏的參數 a 爲指向數組的指針,所以 &a 和 a 的意義不一樣(前者爲指針變量的地址,後者爲指針變量的值)
  //&a 表示指針變量的地址。
  //&a[0] 等效爲 a ,即指針變量的值。
_tprintf(_T(
"func1: &a = 0x%08X; &a[0] = 0x%08X;\n"), &a, &a[0]); } int _tmain(int argc, _TCHAR* argv[]) { char a[10];

  //這裏的 a 是數組名,至關於字面地址,因此 &a 至關於直接寫成 a 。 _tprintf(_T(
"wmain: &a = 0x%08X; &a[0] = 0x%08X;\n"), &a, &a[0]);

   //數組名做爲參數傳遞給其餘函數時,退化爲指針 func1(a);
return 0; }

 

  以 VS2005 編譯,採用默認項目配置(Unicode 編碼),在 Release 版本的輸出結果以下(可見 func1 中的 &a 和其餘輸出不一樣,且相差 4 ,在 debug 版本下此差值是一個較大的數值):cookie

 

  ----------------------------------------------------函數

  Output:post

  ----------------------------------------------------優化

  wmain: &a = 0x0018FF38; &a[0] = 0x0018FF38;編碼

  func1 : &a = 0x0018FF34; &a[0] = 0x0018FF38;url

  ----------------------------------------------------spa

 

  以 IDA 反彙編 Release 版本的可執行文件,獲得 wmain 函數的彙編代碼以下:debug

wmain   proc near
        
var_14  = dword ptr -14h        ; func1 的實際參數(char* a)
var_10  = dword ptr -10h        ; a 的起始地址
var_4   = dword ptr -4          ; 用於 ESP 校驗

sub     esp, 14h                ; 爲臨時變量分配空間
mov     eax, __security_cookie
xor     eax, esp
mov     [esp+14h+var_4], eax    ; 保存 ( ESP ^ _security_cookie ) 到 var_4
push    esi
mov     esi, ds:__imp__wprintf
lea     eax, [esp+18h+var_10]   ; wmain: &a[0] (0018FF38)
push    eax
mov     ecx, eax                ; wmain: &a (0018FF38)
push    ecx
push    offset pStr1            ; 字符串 "wmain: &a = 0x%08X; &a[0] = 0x%08X;\n"
call    esi                     ; __imp__wprintf 打印輸出
lea     edx, [esp+24h+var_10]   ; func1: &a[0] (0018FF38)
mov     eax, edx
push    eax
lea     ecx, [esp+28h+var_14]   ; func1: &a (0018FF34), 參數的地址
push    ecx
push    offset pStr2            ; 字符串 "func1: &a = 0x%08X; &a[0] = 0x%08X;\n"
mov     [esp+30h+var_14], edx   ; 爲實際參數賦值
call    esi                     ; __imp__wprintf 打印輸出
mov     ecx, [esp+30h+var_4]
add     esp, 18h                ; 爲以上兩次 _tprintf 函數調用復原棧指針
pop     esi
xor     ecx, esp
xor     eax, eax
call    __security_check_cookie ; 檢查 ESP 是否被意外破壞
add     esp, 14h                ; 釋放棧上的臨時變量空間
retn

 

  從以上彙編代碼,能夠獲得關於 Release 版本代碼(以優化運行效率爲主要目標)的以下結論:

 

  (1)直接使用 ESP 尋址函數內的臨時變量或參數。

  (2)func1 函數調用被編譯器直接內聯到 wmain 函數體內。在內聯 func1 時,編譯器對代碼作了等效性變換,代碼和棧上數據的順序,與一般函數調用相比有細微差異,但運行結果是等效的。

  (3)在寄存器保護環節,保存了 ESI (目標索引)寄存器,其用意是以 ESI 加載 __imp__wprintf 的運行時(綁定後)地址。(對於默認配置,此函數是來自 VS2005 運行時庫 msvcr80.dll 中的導入函數,函數地址位於導入表中,在加載時被綁定)

 

  下面是根據以上彙編代碼獲得的 wmain 函數的棧上數據示意圖(圖中棧的增加方向爲從下向上,並已經根據 輸出結果 推算出了棧上的虛擬地址):

 

  

 

  上面的表格中包括了兩次對 __imp__wprintf 調用時的參數,其中 __imp__wprintf 的棧幀,除了參數以外的其他部分在表中沒有顯示,便可以認爲上表是第二次 __imp_wprintf 已返回到 wmain 函數時的棧上數據快照,兩次函數調用的復原棧指針(即釋放參數佔用的空間)被合併爲一條指令(add esp, 18h)。表格中的紅色數據,即爲代碼中交由 _tprintf 打印輸出的值。其中 pStr1 和 pStr2 指向位於 .rdata section(只讀數據段)上的字符串(根據項目選項,爲 Unicode 編碼)。

 

  其中 ESP 校驗過程爲,在 wmain 函數的起始位置,爲臨時變量分配空間後,將此時的 ESP 和一個特定常數(_security_cookie)異或,結果保存到 wmain 的第一個臨時變量(var1)中,以後調用了 __imp__wprintf 等其餘函數後,把 ESP 和 var1 異或的結果保存到 ECX 中(此時 ECX 的期待值爲 _security_cookie),而後檢測 ECX 和 _security_cookie 是否相等便可。

 

  【注】:表格中的棧指針校驗值,根據彙編代碼能夠看出,至關於首個出現的函數臨時變量,它的值的意義是,爲臨時變量分配空間後 (T1 時刻),將此時的 ESP 和一個常量值異或,存儲於該臨時變量。在復原棧指針以前(T2 時刻),校驗 ESP 是否吻合 T1 時刻的值。 -- hoodlum1980,2014-4-11

 

  綜合以上圖表,對代碼輸出則能夠比較容易作出解釋:

 

  第一行輸出結果爲 wmain 函數中的 &a 和 a (a 爲數組名)在寫法上等效的體現,在 wmain 裏 a 爲本地數組的數組名(這裏」本地「的含義指的是對其聲明的可見性),若是把 a 理解爲數組,&a 表示數組的存儲地址,若是把 a 理解爲至關於數組元素指針,則 &a 不具備實際物理意義,所以 &a 和 &a[0] 都等效於 a,即數組的起始地址。

 

  第二行輸出結果爲 func1 函數中的 &a 和 a (a 爲指針變量)在乎義上不一樣的體現,a 是一個指向數組的指針變量(以及 func1 的實際參數),&a 表示此指針變量的地址,&a[0] 表示被指向數組的起始地址,即 &a[0] =  a + 0 * sizeof (char) = a (這裏爲數學計算含義),  即指針變量 a 的值。在本例輸出中,func1 的實際參數 a 與」數組起始地址「緊鄰,a 的地址爲 0018FF34h,a 的值爲 0018FF38h(指向 wmain 中的數組)。

 

  所以,本範例的代碼,能夠認爲在原理上即至關於以下代碼:

 

int _tmain(int argc, _TCHAR* argv[])
{
    //main 中的結果:
    char a[10];
    _tprintf(_T("main_: &a = 0x%08X; &a[0] = 0x%08X;\n"), a, a);

    //func1 中的結果
    char *p = a;
    _tprintf(_T("func1: &a = 0x%08X; &a[0] = 0x%08X;\n"), &p, p);

    return 0;
}

 

  【附】

  本文中引用的範例來自於:《神奇的C語言》 中的例子 5 ,http://www.cnblogs.com/linxr/p/3521788.html

相關文章
相關標籤/搜索