在C語言中,假設(shè)我們有這樣的一個函數(shù):
int function(int a,int b)
調(diào)用時只要用result= function(1,2)這樣的方式就可以使用這個函數(shù)。但是,當(dāng)高級
語言被編譯成計(jì)算機(jī)可以識別的機(jī)器碼時,有一個問題就凸現(xiàn)出來:在CPU中,計(jì)算機(jī)沒有辦法知道一個函數(shù)調(diào)用需要多少個、什么樣的參數(shù),也沒有硬件可以保存這些參數(shù)。
也就是說,計(jì)算機(jī)不知道怎么給這個函數(shù)傳遞參數(shù),傳遞參數(shù)的工作必須由函數(shù)調(diào)用者和函數(shù)本身來協(xié)調(diào)。為此,計(jì)算機(jī)提供了一種被稱為棧的數(shù)據(jù)結(jié)構(gòu)來支持參數(shù)傳遞。
棧是一種先進(jìn)后出的數(shù)據(jù)結(jié)構(gòu),棧有一個存儲區(qū)、一個棧頂指針。棧頂指針指向堆棧中第一個可用的數(shù)據(jù)項(xiàng)(被稱為棧頂)。用戶可以在棧頂上方向棧中加入數(shù)據(jù),這個操作被稱為壓棧(Push),壓棧以后,棧頂自動變成新加入數(shù)據(jù)項(xiàng)的位置,棧頂指針也隨之修改。用戶也可以從堆棧中取走棧頂,稱為彈出棧(pop),彈出棧后,棧頂下的一個元素變成棧頂,棧頂指針隨之修改。
函數(shù)調(diào)用時,調(diào)用者依次把參數(shù)壓棧,然后調(diào)用函數(shù),函數(shù)被調(diào)用以后,在堆棧中取得數(shù)據(jù),并進(jìn)行計(jì)算。函數(shù)計(jì)算結(jié)束以后,或者調(diào)用者、或者函數(shù)本身修改堆棧,使堆�;謴�(fù)原裝。
在參數(shù)傳遞中,有兩個很重要的問題必須得到明確說明:
當(dāng)參數(shù)個數(shù)多于一個時,按照什么順序把參數(shù)壓入堆棧函數(shù)調(diào)用后,由誰來把堆�;謴�(fù)原裝在高級語言中,通過函數(shù)調(diào)用約定來說明這兩個問題。常見的調(diào)用約定有:
stdcall、cdecl、fastcall、thiscall、naked call
stdcall調(diào)用約定
stdcall很多時候被稱為pascal調(diào)用約定,因?yàn)閜ascal是早期很常見的一種教學(xué)用計(jì)算機(jī)程序設(shè)計(jì)語言,其語法嚴(yán)謹(jǐn),使用的函數(shù)調(diào)用約定就是stdcall。在Microsoft C++系列的C/C++編譯器中,常常用PASCAL宏來聲明這個調(diào)用約定,類似的宏還有WINAPI和CALLBACK。
stdcall調(diào)用約定聲明的語法為(以前文的那個函數(shù)為例):
int __stdcall function(int a,int b)
stdcall的調(diào)用約定意味著:1)參數(shù)從右向左壓入堆棧,2)函數(shù)自身修改堆棧 3)函數(shù)名自動加前導(dǎo)的下劃線,后面緊跟一個@符號,其后緊跟著參數(shù)的尺寸以上述這個函數(shù)為例,參數(shù)b首先被壓棧,然后是參數(shù)a,函數(shù)調(diào)用function(1,2)調(diào)用處翻譯成匯編語言將變成:
push 2 第二個參數(shù)入棧
push 1 第一個參數(shù)入棧
call function 調(diào)用參數(shù),注意此時自動把cs:eip入棧
而對于函數(shù)自身,則可以翻譯為:
push ebp 保存ebp寄存器,該寄存器將用來保存堆棧的棧頂指針,可以在函數(shù)退出
時恢復(fù)movebp,esp保存堆棧指針