久久久久久久999_99精品久久精品一区二区爱城_成人欧美一区二区三区在线播放_国产精品日本一区二区不卡视频_国产午夜视频_欧美精品在线观看免费

標題: C語言函數調用的底層機制 [打印本頁]

作者: 51黑tt    時間: 2016-3-6 00:13
標題: C語言函數調用的底層機制
這是一篇介紹C語言中的函數調用是如何用實現的文章。寫給那些對C語言各種行為的底層實現感興趣人的入門級文章。如果你是C語言或者匯編、底層技術
的老鳥或是對這個問題不感興趣,那么這篇文章只會耽誤您的時間,您大可不必閱讀他。當然如果前輩們愿意為我指出不足,我將十分感謝您的指導,并對耽誤您寶
貴的時間致歉。
好了,廢話少說!要研究這個問題,讓我們先打開VC++吧。最好是6.0的,:-P。(什么你沒有VC++,倒!....趕快裝一個!@#$,要快!)
首先,讓我們在VC++里建立一個Win32 Console Application項目,并建立主文件fun.c。并輸入以下內容。
int fun(int a, int b) {
   a = 0x4455;
   b = 0x6677;
   return a + b;
}

int main() {
    fun(0x8899,0x1100);
    return 0;
}

后,最關鍵的是在項目設置里關閉優化功能。也就是把Project->Setting->C/C++->Optimizations選
為Disabled。編譯器的優化在分析底層實現時大多數情況不太受歡迎。 按鍵盤上的F10鍵,進入單步調試模式(Step

Over)。看到你的main函數左側有個黃色的小箭頭了嗎?那個就是程序即將執行的語句。按Alt +

8。打開反編譯窗口,看到匯編語句了嗎?是不是想這個樣子
==> 00401078   push        1100h
    0040107D   push        8899h
    00401082   call        @ILT+5(fun) (0040100a)
    00401087   add         esp,8

到兩個PUSH指令了嗎?再看看后面的數字,不正是我們要傳遞的參數嗎。奇怪阿?我們明明是先傳遞的0x8899怎么反倒先push

1100h呢?呵呵,這個現象就叫Calling

conversion。究竟是何方神圣,我在后面會詳細的給你解釋的。先別著急。隨后的Call指令的作用就是開始調用函數了。
接下來關掉反匯編窗口,在源代碼窗口按F11(Step

Into)進入函數體。當看到那個黃色的小箭頭指向函數名的時候再調出反匯編窗口(Alt+8)。你會看到類似下面的代碼:
1:    int fun(int a, int b) {
00401000   push        ebp
00401001   mov         ebp,esp

00401003   sub         esp,40h
00401006   push        ebx
00401007   push        esi
00401008   push        edi
00401009   lea         edi,[ebp-40h]
0040100C   mov         ecx,10h
00401011   mov         eax,0CCCCCCCCh
00401016   rep stos    dword ptr [edi]
2:       a = 0x4455;
00401018   mov         dword ptr [ebp+8],4455h
3:       b = 0x6677;
0040101F   mov         dword ptr [ebp+0Ch],6677h
4:       return a + b;
00401026   mov         eax,dword ptr [ebp+8]
00401029   add         eax,dword ptr [ebp+0Ch]


5:    }
0040102C   pop         edi
0040102D   pop         esi
0040102E   pop         ebx
0040102F   mov         esp,ebp
00401031   pop         ebp
00401032   ret
VC++就是好,還在難懂的匯編語句前加入了C語言的源代碼。不過同時也有不少我們不需要的代碼。因此,你只需要關心紅色的部分就可以了。
奇怪阿?不是參數都用push傳遞了嗎?怎么沒看到被pop出來?問題其實是這樣,當你調用Call進入函數的時候Call背著你做了一件事。call把
它下一條語句的地址push進了堆棧。(旁人:
什么!這是為什么?)原因很簡單,因為函數調用完了,要用ret返回。而ret怎么知道返回哪里呢?對了,

ret指令pop了call指令push給他的地址(搞清楚這個關系哦),然后返回到了這個地址。call和ret配合的如此絕妙,一個PUSH一個

POP肯定不會讓堆棧不平衡的(老外叫no stack unwinding)。現在明白了,如果你來個pop

eax,那eax里面是什么?當然是ret要用的返回地址了。好啦,你要是pop

eax就等于搶了ret要用的東西了。不論曾程序流程和道德標準上你做的都不對 :-P。
可是怎么在函數體里使用參數呢?問題其實并不難,既然參數在堆棧里我們就可以使用esp(堆棧指針)來訪問了。不過,我相信你也想到了。esp是個經常變
化的值。一旦,函數里出現pop或push他就會變化。這樣很不容易定位參數的于內存中的位置。因此,我們需要一個不會變化的東西作為訪問參數的基準。看
看函數體的開頭部分:
00401000   push        ebp
00401001   mov         ebp,esp

用push ebp保存了原來ebp的值再把esp的值給ebp。原來ebp就是用來做基準的。也難怪他被稱為ebp(Base

Pointer)。很自然ret返回前的pop

ebp就是恢復原來ebp的數值嘍。當然一定要恢復,因為函數里也可以調用函數嘛。每個函數都用ebp,自然要保證使用完后完璧歸趙了。現在當函數執行到

mov ebp, esp后堆棧應該變成這個樣子了。
/-------------------\  Higher Address
| 參數2:  0x1100h |  
+-----------------+
| 參數1:  0x8899h |
+-----------------+
|   函數返回地址  |
|    0x00401087   |
+-----------------+
|       ebp       |
\-------------------/   Lower Address <== stack pointer
& ebp all point to here, now

于我們在VC++上使用的int類型是一個32位類型,ebp和函數返回值也是32位的。因此每個量要占去4個字節。另外還需要注意堆棧的擴展方向是高地
址到低地址。有了這些指示。我們就可以分析出,第一個參數的地址是ebp + 08h,第二個參數就是ebp + 0ch。看看反匯編的代碼:
2:       a = 0x4455;
00401018   mov         dword ptr [ebp+8],4455h
3:       b = 0x6677;
0040101F   mov         dword ptr [ebp+0Ch],6677h
與我們的計算吻合。之后呢: 00401031   pop         ebp
00401032   ret
將ebp原來的數值完璧歸趙,調用ret指令,ret指令pop出返回地址,之后返回到調用函數的call指令的下一條語句。ret之后,堆棧應該變成這個樣子了 /-------------------\  Higher Address
| 參數2:  0x1100h |  
+-----------------+
| 參數1:  0x8899h |
\-------------------/   Lower Address  <== stack pointer

哈,問題出現了,再函數返回后堆棧出現了不平衡的情況(Stack Unwinding)。怎么辦呢?好辦啊,直接 pop cx pop cx
把堆棧平衡過來就好了。幸好我們只有兩個參數,要是有20個的話,那就要有20個pop

cx。不說影響美觀,程序效率也會很低。所以VC++使用了這個辦法解決問題:
00401082   call        @ILT+5(fun) (0040100a)
00401087   add         esp,8
看紅色的語句,直接將esp的值加8,讓堆棧變成 /-------------------\  Higher Address <== stack pointer
| 參數2:  0x1100h |  
+-----------------+
| 參數1:  0x8899h |
\-------------------/   Lower Address
通過改變esp從根本上解決了Stack unwinding。(push,pop指令本質上不就是通過改變esp來實現堆棧平衡的嗎) 現在,明白了函數如何傳遞參數,如何調用,如何返回。下一個問題就是看看函數如何傳遞返回值了。相信你早就注意到了 4:       return a + b;
00401026   mov         eax,dword ptr [ebp+8]
00401029   add         eax,dword ptr [ebp+0Ch]

見,函數正式用eax寄存器來保存返回值的。如果你想使用函數的返回值,那么一定要在函數一返回就把eax寄存器的值讀出來。至于為什么不用ebx,

ecx...,這個雖然沒有規定,但是習慣上大家都是用eax的。而且windows程序中也明確指出了,函數的返回值必須放入eax內。

OK,現在來解決什么是calling

conversion這個歷史遺留問題。如果認真思考過,你一定想函數的參數為什么偏用堆棧轉遞呢,寄存器不也可以傳遞嗎?而且很快阿。參數的傳遞順序不
一定要是由后到前的,從前到后傳遞也不會出現任何問題啊?再有為什么一定要等到函數返回了再處理堆棧平衡的問題呢,能否在函數返回前就讓堆棧平衡呢?
所有上述提議都是絕對可行的,而他們之間不同的組合就造就了函數不同的調用方法。也就是你常看到或聽到的stdcall,pascal,

fastcall,WINAPI,cdecl等等。這些不同的處理函數調用方式就叫做calling convention。
默認情況下C語言使用的是cdecl方式,也就是上面提到的。參數由右到左進棧,調用函數者處理堆棧平衡。如果你在我們剛才的程序中fun函數前加入

__stdcall,再來用上面的方法分析一下。
8:        fun(0x8899,0x1100);
00401058   push        1100h  ; <== 參數仍然是由右到左傳遞的
0040105D   push        8899h   
00401062   call        fun (00401000)
;<== 這里沒有了 add esp, 08h

1:    int __stdcall fun(int a, int b) {
00401000   push        ebp
00401001   mov         ebp,esp
00401003   sub         esp,40h
00401006   push        ebx
00401007   push        esi
00401008   push        edi
00401009   lea         edi,[ebp-40h]
0040100C   mov         ecx,10h
00401011   mov         eax,0CCCCCCCCh
00401016   rep stos    dword ptr [edi]
2:       a = 0x4455;
00401018   mov         dword ptr [ebp+8],4455h
3:       b = 0x6677;
0040101F   mov         dword ptr [ebp+0Ch],6677h
4:       return a + b;
00401026   mov         eax,dword ptr [ebp+8]
00401029   add         eax,dword ptr [ebp+0Ch]
5:    }
0040102C   pop         edi
0040102D   pop         esi
0040102E   pop         ebx
0040102F   mov         esp,ebp
00401031   pop         ebp
00401032   ret         8; <== ret 取出返回地址后,
                       ; 給esp加上 8。看!堆棧平衡在函數內完成了。
                       ; ret指令這個語法設計就是專門用來實現函數
                       ; 內完成堆棧平衡的

是得出結論,stdcall是由右到左傳遞參數,被調用函數恢復堆棧的calling convention. 其他幾種calling

convention的修飾關鍵詞分別是__pascal,__fastcall,

WINAPI(這個要包含windows.h才可以用)。現在,你可以用上面說的方法自己分析一下他們各自的特點了。






歡迎光臨 (http://www.zg4o1577.cn/bbs/) Powered by Discuz! X3.1
主站蜘蛛池模板: 国产精品99久久久久久宅男 | 午夜色婷婷 | 成人午夜免费福利视频 | 精品久久久网站 | 中文字幕国产一区 | 天天干天天干 | 草樱av | 久久99国产精品久久99果冻传媒 | 欧美影院 | 久久精品一区二区三区四区 | 亚洲精品一区二区久 | 国产农村妇女精品一二区 | av一区二区三区四区 | 亚洲视频中文字幕 | 99亚洲精品 | 色欧美片视频在线观看 | 欧美日韩高清在线一区 | 国产成人aⅴ | 国产福利在线小视频 | 涩涩视频网站在线观看 | 国产成人高清成人av片在线看 | av免费在线观看网站 | 四虎影院一区二区 | 免费看一区二区三区 | 久久久久久久成人 | 日韩欧美国产一区二区 | 久久久精 | 一级免费看片 | 欧美日韩精品一区二区三区四区 | av免费网站在线观看 | 久久久国产一区 | 国产伦精品一区二区三区四区视频 | 免费久 | 91高清视频在线 | 剑来高清在线观看 | 成人一区二区视频 | 日本精品久久久一区二区三区 | 成年人网站在线观看视频 | 亚洲国产成人精品在线 | 精品产国自在拍 | 综合色播 |