在C語言中,假設我們有這樣的一個函數:
int function(int a,int b)
調用時隻要用result = function(1,2)這樣的方式就可以使用這個函數。
但是,當進階語言被編譯成計算機可以識别的機器碼時,有一個問題就凸現出來:在CPU中,計算機沒有辦法知道一個函數調用需要多少個、什麼樣的參數,也沒有硬體可以儲存這些參數。也就是說,計算機不知道怎麼給這個函數傳遞參數,傳遞參數的工作必須由函數調用者和函數本身來協調。為此,計算機提供了一種被稱為棧的資料結構來支援參數傳遞。
棧的簡述:是一種先進後出的資料結構,棧有一個存儲區、一個棧頂指針。棧頂指針指向堆棧中第一個可用的資料項(被稱為棧頂)。使用者可以在棧頂上方向棧中加入資料,這個操作被稱為壓棧(Push),壓棧以後,棧頂自動變成新加入資料項的位置,棧頂指針也随之修改。使用者也可以從堆棧中取走棧頂,稱為彈出棧(pop),彈出棧後,棧頂下的一個元素變成棧頂,棧頂指針随之修改。
函數調用時,調用者依次把參數壓棧,然後調用函數,函數被調用以後,在堆棧中取得資料,并進行計算。函數計算結束以後,或者調用者、或者函數本身修改堆棧,使堆棧恢複原裝。
在參數傳遞中,有兩個很重要的問題必須得到明确說明:
- 當參數個數多于一個時,按照什麼順序把參數壓入堆棧
- 函數調用後,由誰來把堆棧恢複原裝
在進階語言中,通過函數調用約定來說明這兩個問題。
函數調用約定
是函數調用者和被調用的函數體之間關于參數傳遞、傳回值傳遞、堆棧清除、寄存器使用的一種約定。
常見的調用約定有:
- stdcall(pascal)
- cdecl
- fastcall
- thiscall
- naked call
簡述:
-
stdcall(pascal)–Standard Call的縮寫,C++的标準調用方式
在Microsoft C++系列的C/C++編譯器中,常常用PASCAL宏來聲明這個調用約定,類似的宏還有WINAPI和CALLBACK。
一般WIN32的函數都是__stdcall
聲明的文法為:
stdcall的調用約定意味着:int __stdcall function(int a,int b)
- 參數從右向左壓入堆棧
- 函數自身清理堆棧
- 函數名自動加前導的下劃線,後面緊跟一個@符号,其後緊跟着參數的尺寸
-
cdecl–C Declaration的縮寫,C語言預設的調用約定
聲明的文法為:
cdecl調用約定意味着:int function (int a ,int b) //不加修飾就是C調用約定 int __cdecl function(int a,int b)//明确指出C調用約定
- 參數從右向左壓入堆棧
- 調用者負責清理堆棧
- C調用約定允許函數的參數的個數是不固定的,這也是C語言的一大特色。
- 僅在函數名前加上一個下劃線字首,格式為_functionname。
-
fastcall
聲明的文法為:
fastcall調用約定意味着:int fastcall function(int a,int b)
- 函數的第一個和第二個DWORD參數(或者尺寸更小的)通過ecx和edx傳遞,其他參數通過從右向左的順序壓棧
- 函數自身清理堆棧
- 函數名修改規則同stdcall:函數名自動加前導的下劃線,後面緊跟一個@符号,其後緊跟着參數的尺寸
-
thiscall–C++類成員函數預設的調用約定。
thiscall是唯一一個不能明确指明的函數修飾,因為thiscall不是關鍵字。由于成員函數調用還有一個this指針,是以必須特殊處理,thiscall意味着:
- 參數從右向左入棧
- 如果參數個數不确定,this指針在所有參數壓棧後被壓入堆棧。如果參數個數确定,this指針通過ecx傳遞給被調用者。
- 如果參數個數不确定,調用者清理堆棧,否則函數自己清理堆棧
-
naked call
一個很少見的調用約定,一般用于實模式驅動程式設計
C++編譯時函數名修飾約定規則:
- __stdcall調用約定:
- 以“?”辨別函數名的開始,後跟函數名;
- 函數名後面以“@@YG”辨別參數表的開始,後跟參數表;
- 參數表以代号表示:
- X–void ,
- D–char,
- E–unsigned char,
- F–short,
- H–int,
- I–unsigned int,
- J–long,
- K–unsigned long,
- M–float,
- N–double,
- _N–bool,
-
PA–表示指針,後面的代号表明指針類型,如果相同類型的指針連續出現,以“0”代替,一個“0”代表一次重複
…
- 參數表的第一項為該函數的傳回值類型,其後依次為參數的資料類型,指針辨別在其所指資料類型前;
- 參數表後以“@Z”辨別整個名字的結束,如果該函數無參數,則以“Z”辨別結束。
int Test1(char *var1,unsigned long)-----“[email protected]@YGHPADK@Z” void Test2() -----“[email protected]@YGXXZ”
-
__cdecl調用約定:
規則同上面的_stdcall調用約定,隻是參數表的開始辨別由上面的“@@YG”變為“@@YA”。
-
__fastcall調用約定:
規則同上面的_stdcall調用約定,隻是參數表的開始辨別由上面的“@@YG”變為“@@YI”。
函數調用約定導緻的常見問題
如果定義的約定和使用的約定不一緻,則将導緻堆棧被破壞,導緻嚴重問題,下面是兩種常見的問題:
- 函數原型聲明和函數體定義不一緻
- DLL導入函數時聲明了不同的函數約定
調用函數的代碼和被調函數必須采用相同的函數的調用約定,程式才能正常運作。