呼叫約定是呼叫方和被呼叫方對於函式如何呼叫的乙個明確的約定,只有雙方都遵守同樣的約定函式才能被正確的呼叫。
int foo(int n, float m)
如果函式的呼叫方在傳遞引數室先壓入引數n,再壓入引數m,而函式則認為呼叫方應該先先壓入引數m,再壓入引數n,那麼在內部中m與n的值將會被交換。
再者,如果函式的呼叫方決定利用暫存器傳遞引數,而函式本身仍然認為引數通過棧傳遞,那麼顯然函式無法獲取正確的引數。
所以函式的呼叫方和被呼叫方對於函式如何呼叫需要有乙個明確的約定。
乙個呼叫慣例一般會規定以下方面的內容
下表介紹了幾項主要的呼叫慣例的內容。
呼叫慣例
出棧方引數傳遞
名字修飾
cdecl
函式呼叫方
從右至左的順序壓引數入棧
下劃線+函式名
stdcall
函式本身
從左至右的順序壓引數入棧
下劃線+函式名+@+引數的位元組數,如函式int func(int a,double b)的修飾名是*func@12
fastcall
函式本身
頭兩個dword(4位元組)型別或者佔更少位元組的引數被放入暫存器,其他剩下的引數按從右到左的順序壓入棧
@+函式名+@+引數的位元組數
pascall
函式本身
從左至右的順序壓引數入棧
較為複雜,參加pascal文件
thiscall
c++自己專用於類成員函式的呼叫。其特點隨編譯器不同而不同,在vc裡是this指標存放於ecx暫存器,引數從右到左壓棧,而對於gcc、thiscall和cdecl完全一樣,只是將this看作是函式的第乙個引數。
舉例說明:
在c語言中預設使用cdecl。
所以上面函式foo的宣告,完整為
int *cdecl foo(int n, float m)
因此foo被修飾之後變成*foo,按照cdecl的引數傳遞方式,具體的堆疊操作為:
將m壓入棧
將n壓入棧
呼叫*foo,該步驟可細分為:
跳轉到*foo執行
當函式返回之後引數出棧:sp = sp + 8(由於不需要得到出棧的資料,直接調整棧頂位置即可)。
[外鏈轉存失敗,源站可能有防盜煉機制,建議將儲存下來直接上傳(img-sskxjccr-1599269554633)(./image/mn出棧.png)]
然後在foo裡面要儲存一系列的暫存器,包括函式呼叫方的ebp暫存器,以及為a和b兩個區域性變數分配的空間,最終棧的構成為:
[外鏈轉存失敗,源站可能有防盜煉機制,建議將儲存下來直接上傳(img-icwqfkvx-1599269554636)(./image/foo_mn棧構成.png)]
在以上布局中,如果想訪問變數n
,實際的位址是使用ebp+8
。當foo
返回的時候,程式首先會使用pop恢復儲存在棧裡的暫存器,然後從棧裡取得返回位址,返回到呼叫方。呼叫方再調整esp
將堆疊恢復。
另附esp與emp詳解
esp:棧指標暫存器(extended stack pointer),其內存放著乙個指標,該指標永遠指向系統棧最上面乙個棧幀的棧頂。
棧指標暫存器*(extended stack pointer),其內存放著乙個指標,該指標永遠指向系統棧最上面乙個棧幀的棧頂。
emp:基址指標暫存器(extended base pointer),其內存放著乙個指標,該指標永遠指向系統棧最上面乙個棧幀的底部。
函式呼叫慣例
函式的呼叫方與被呼叫方對於函式如何呼叫須要乙個明確的約定,這樣的約定就叫作呼叫慣例。乙個呼叫慣例一般會規定如下幾方面的內容 1.函式引數的傳遞順序及方式 函式引數的傳遞有多種方式,常見的是通過棧傳遞。函式的呼叫方將引數壓入棧中,函式自己再從棧中取出引數。對於有多個引數的函式,呼叫慣例需要約定函式呼叫...
棧與呼叫慣例
每個程序分配的記憶體由很多部分組成,通常稱為 段 1.文字段 包含了程序執行的程式二進位制機器語言指令,唯讀,可共享,因為多個程序可同時執行同一程式 2.初始化資料段 包含顯式初始化的全域性變數和靜態變數 3.未初始化資料段 也稱為bss段,包含未進行顯示初始化的全域性變數和靜態變數。為什麼分開放呢...
VC 中的函式呼叫慣例
我們知道在進行函式呼叫時,有幾種呼叫方法,主要分為c式,pascal式.在c和c 中c式呼叫是預設的,類的成員函式預設呼叫為 stdcall。二者是有區別的,下面我們用例項說明一下 1.cdecl c和c 預設呼叫方式 例子 void input int m,int n 相當於void cdecl ...