函數(shù)調(diào)用約束

什么是調(diào)用約定

函數(shù)的調(diào)用約定,顧名思義就是對(duì)函數(shù)調(diào)用的一個(gè)約束和規(guī)定(規(guī)范),描述了函數(shù)參數(shù)是怎么傳遞和由誰(shuí)清除堆棧的。

  • 函數(shù)參數(shù)的壓棧順序
  • 由調(diào)用者還是被調(diào)用者把參數(shù)彈出棧
  • 以及產(chǎn)生函數(shù)修飾名的方法

函數(shù)組成:返回值類型 調(diào)用約定 函數(shù)名(參數(shù)列表...)

void __cdecl function(int a,int b);
void __stdcall function(int a,int b);

上面的__cdecl和__stdcall就是調(diào)用約定,其中__cdecl是C和C++默認(rèn)的調(diào)用約定;譯器默認(rèn)會(huì)為我們使用__cdecl調(diào)用約定;常見(jiàn)的調(diào)用約定有__cdecl、__stdcall、fastcall,應(yīng)用最廣泛的是__cdecl和__stdcall,下面我們會(huì)詳細(xì)進(jìn)行講述。。還有一些不常見(jiàn)的,如 __pascal、__thiscall、__vectorcall。

聲明和定義處調(diào)用約定必須要相同

在VC++中,調(diào)用約定是函數(shù)類型的一部分,因此函數(shù)的聲明和定義處調(diào)用約定要相同,不能只在聲明處有調(diào)用約定,而定義處沒(méi)有或與聲明不同。

int __cdecl function(int a,int b);
int __cdecl function(int a,int b)
{
      return a+b;
}

函數(shù)的調(diào)用過(guò)程

要深入理解函數(shù)調(diào)用約定,你須要了解函數(shù)的調(diào)用過(guò)程和調(diào)用細(xì)節(jié)
假設(shè)函數(shù)A調(diào)用函數(shù)B,我們稱A函數(shù)為”調(diào)用者”,B函數(shù)為“被調(diào)用者”。如下面的代碼

int B(int a,int b)
{
    return a+b;
}
void A()
{
    std::cout()<<B(5,6)<<std::endl;
}

函數(shù)調(diào)用過(guò)程可以這么描述:

  • 先將調(diào)用者(A)的堆棧的基址(ebp)入棧,以保存之前任務(wù)的信息。
  • 然后將調(diào)用者(A)的棧頂指針(esp)的值賦給ebp,作為新的基址(即被調(diào)用者B的棧底)。
  • 然后在這個(gè)基址(被調(diào)用者B的棧底)上開(kāi)辟(一般用sub指令)相應(yīng)的空間用作被調(diào)用者B的??臻g。
  • 函數(shù)B返回后,從當(dāng)前棧幀的ebp即恢復(fù)為調(diào)用者A的棧頂(esp),使棧頂恢復(fù)函數(shù)B被調(diào)用前的位置;然后調(diào)用者A再?gòu)幕謴?fù)后的棧頂可彈出之前的ebp值(可以這么做是因?yàn)檫@個(gè)值在函數(shù)調(diào)用前一步被壓入堆棧)。這樣,ebp和esp就都恢復(fù)了調(diào)用函數(shù)B前的位置,也就是?;謴?fù)函數(shù)B調(diào)用前的狀態(tài)。
    這個(gè)過(guò)程在AT&T匯編中通過(guò)兩條指令完成
move %ebp ,%esp
pop  %ebp

__cdecl的特點(diǎn)

__cdecl 是 C Declaration 的縮寫(xiě),表示 C 和 C++ 默認(rèn)的函數(shù)調(diào)用約定。是C/C++和MFCX的默認(rèn)調(diào)用約定;

  • 按從右至左的順序壓參數(shù)入棧、。
  • 由調(diào)用者把參數(shù)彈出棧。切記:對(duì)于傳送參數(shù)的內(nèi)存棧是由調(diào)用者來(lái)維護(hù)的,返回值在EAX中。因此對(duì)于像printf這樣可變參數(shù)的函數(shù)必須用這種約定。
  • 編譯器在編譯的時(shí)候?qū)@種調(diào)用規(guī)則的函數(shù)生成修飾名的時(shí)候,在輸出函數(shù)名前加上一個(gè)下劃線前綴,格式為_(kāi)function。如函數(shù)int add(int a, int b)的修飾名是_add。

從代碼和程序調(diào)試的層面考慮,參數(shù)的壓棧順序和棧的清理我們都不用太觀注,因?yàn)檫@是編譯器的決定的,我們改變不了。但第三點(diǎn)卻常常困擾我們,因?yàn)槿绻慌宄@點(diǎn),在多個(gè)庫(kù)之間(如dll、lib、exe)相互調(diào)用、依賴時(shí)常常出出現(xiàn)莫名其妙的錯(cuò)誤。這個(gè)我在后面章節(jié)會(huì)進(jìn)行詳細(xì)介紹。

__stdcall的特點(diǎn)

__stdcall是Standard Call的縮寫(xiě),是C++的標(biāo)準(zhǔn)調(diào)用方式,當(dāng)然這是微軟定義的標(biāo)準(zhǔn),__stdcall通常用于Win32 API中(可查看WINAPI的定義)。

  • 按從右至左的順序壓參數(shù)入棧。
  • 由被調(diào)用者把參數(shù)彈出棧。切記:函數(shù)自己在退出時(shí)清空堆棧,返回值在EAX中。
  • __stdcall調(diào)用約定在輸出函數(shù)名前加上一個(gè)下劃線前綴,后面加上一個(gè)“@”符號(hào)和其參數(shù)的字節(jié)數(shù),格式為_(kāi)function@number。如函數(shù)int sub(int a, int b)的修飾名是_sub@8。

__fastcall的特點(diǎn)

__fastcall調(diào)用的主要特點(diǎn)就是快,因?yàn)樗峭ㄟ^(guò)寄存器來(lái)傳送參數(shù)的。

  • 實(shí)際上__fastcall用ECX和EDX傳送前兩個(gè)DWORD或更小的參數(shù),剩下的參數(shù)仍自右向左壓棧傳送,被調(diào)用的函數(shù)在返回前清理傳送參數(shù)的內(nèi)存棧。
  • __fastcall調(diào)用約定在輸出函數(shù)名前加上一個(gè)“@”符號(hào),后面也是一個(gè)“@”符號(hào)和其參數(shù)的字節(jié)數(shù),格式為@function@number,如double multi(double a, double b)的修飾名是@multi@16。
  • __fastcall和__stdcall很象,唯一差別就是頭兩個(gè)參數(shù)通過(guò)寄存器傳送。注意通過(guò)寄存器傳送的兩個(gè)參數(shù)是從左向右的,即第1個(gè)參數(shù)進(jìn)ECX,第2個(gè)進(jìn)EDX,其他參數(shù)是從右向左的入棧,返回仍然通過(guò)EAX。

__thiscall的特點(diǎn)

__thiscall是C++類成員函數(shù)缺省的調(diào)用約定,但它沒(méi)有顯示的聲明形式。。因?yàn)樵贑++類中,成員函數(shù)調(diào)用還有一個(gè)this指針參數(shù),因此必須特殊處理,thiscall調(diào)用約定的特點(diǎn):

  • 參數(shù)入棧:參數(shù)從右向左入棧
  • this指針入棧:如果參數(shù)個(gè)數(shù)確定,this指針通過(guò)ecx傳遞給被調(diào)用者;如果參數(shù)個(gè)數(shù)不確定,this指針在所有參數(shù)壓棧后被壓入棧。
  • ?;謴?fù):對(duì)參數(shù)個(gè)數(shù)不定的,調(diào)用者清理?xiàng)?,否則函數(shù)自己清理?xiàng)!?/li>
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容