過程和選擇
不說那麼多鬼言鬼語,開始今天的內容。
過程(函數)調用
過程蘊含了抽象的思想,提供了代碼封裝的方式(information hiding和interface的內容,c++系列筆記中會有)
過程不僅僅包括函數,但是我們這裏以調用函數理解過程調用中的存儲方式等等。
在設計過程調用的原理時,人們採用了最低要求策略的方法。
概述:存放參數→調用add(add函數首地址)→add取出參數,執行相關過程→存放返回結果→返回main
過程概述
1 參數的傳遞
-參數通過stack傳遞
棧數據結構 這個部分是過程的棧幀(stack fram)
- P將入口參數(實參)存儲在Q能訪問到的地方
2 轉移指令
P保存返回地址(call指令下一條指令的地址),然後控制轉移到Q(EIP設置爲Q的首地址)
3 爲Q 分配空間, Q保存P的現場,爲自己的非靜態局部變量分配空間
4 Q恢復現場,釋放空間,並提供一個返回值
5 取出返回地址,控制轉移到P(ret指令)(EIP設置爲call指令下一條指令的地址)
保存現場的解釋:保存GPR中的內容,因爲調用過程和被調用過程用一套寄存器,需要標識哪些寄存器可用和使用規範。
寄存器使用約定(分配寄存器的規則)
- P保存的寄存器:EAX EDX ECX (Q可以隨意使用,用完以後不需要恢復,ps:大廚從來不把原料歸到原位),如果P在Q返回後要使用,需要先提前告訴Q,先保存再恢復使用
- 被調用者Q保存寄存器:EBX ESI EDI(必須先保存再使用,使用完記得恢復原樣)
- EBP:幀指針寄存器 ESP:棧指針寄存器
爲了減少準備和結束的開銷,Q儘量使用EAX ECX EDX
通過改變esp的值增加或釋放空間
調用過程棧和棧幀的變化
入口參數的位置
實例1
-
準備階段:
pushl %ebp
將EBP舊址壓棧,此時ESP指向R[EBP],
movl %esp,%ebp
將esp的值傳給ebp,此時兩個指針都指向棧頂,
每個函數起始指令都是push和mov
subl $24 %esp
改寫esp的位置,提供空間存放參數和保留空間 -
分配局部變量
-
準備入口參數
-
call add
-
返回地址的值在EIP中,返回參數在EAX中
-
返回地址是call指令下一條指令的地址
-
入口參數按照從右到左存放
-
按值傳遞&按地址傳遞
實例2
轉移控制
P到Q:EIP設置爲Q函數首地址
Q到P:call指令會把call指令下一條指令的地址壓入棧中
ret指令從棧中取出地址,將EIP設置爲該值
遞歸函數的實現
舉例
每次遞歸調用會增加棧幀,所以空間開銷大,時間開銷大
x86-64中的過程調用
在x86-64/Linux平臺上用以下命令執行編譯操作,得到與IA-32兼容的彙編指令代碼$ gcc -01 -S -m32 sample.c
在x86-64/Linux平臺上用以下命令執行編譯操作,得到x86-64彙編指令代碼$ gcc -01 -S -m64 sample.c
(可以省略-m64)
棧的分配
32位系統在棧中傳遞,64位直接在寄存器中傳遞,將參數放入寄存器中
-通過通用寄存器傳送參數,很多過程不用訪問棧,故執行時間比IA-32代碼更短
-最多可有6個整型或指針型參數通過寄存器傳遞
-超過6個入口參數時,後面的通過棧來傳遞(32位直接使用棧)
-在棧中傳遞的參數若是基本類型,則都被分配8個字節
- call (或Callq )將64位返址保存在棧中之前,執行R[rsp]←R[rsp]-8(64位架構返回地址一定是64位)
- ret從棧中取出64位返回地址後,執行R[rsp]←R[rsp]+8
- 64位架構中也有調用者保存寄存器和被調用者保存寄存器
數據傳送
x86-64中,大多數的數據傳遞都是通過寄存器實現的,P調用Q時,P的參數複製到適當的寄存器中,Q返回P時,P訪問R[%rax](32位中是eax)中的返回值
控制(更新中)
條件
if-else語句
- 條件轉移指令
- 無條件轉移指令 goto done
switch-case語句
推薦小鬼新歌《你最近還好嗎》,狠起來連自己都罵,每一天都是嶄新的一天!(吐槽:別人早安鈴都是叫早起,你的早安鈴是推薦新EP,我要換成尤膩膩的早安鈴了)