VC 中的函式呼叫慣例

2021-04-12 23:00:22 字數 2586 閱讀 7040

我們知道在進行函式呼叫時,有幾種呼叫方法,主要分為c式,pascal式.在c和c++中c式呼叫是預設的,類的成員函式預設呼叫為_stdcall。二者是有區別的,下面我們用例項說明一下:

1. __cdecl :c和c++預設呼叫方式

例子:

void input( int &m,int &n);/*相當於void __cdecl input(int &m,int &n);*/

以下是相應的彙編**:

00401068 lea eax,[ebp-8] ;取[ebp-8]位址(ebp-8),存到eax

0040106b push eax ;然後壓棧

0040106c lea ecx,[ebp-4] ;取[ebp-4]位址(ebp-4),存到ecx

0040106f push ecx ;然後壓棧

00401070 call @ilt+5(input) (0040100a);然後呼叫input函式

00401075 add esp,8 ;恢復棧

從以上呼叫input函式的過程可以看出:在呼叫此函式之前,首先壓棧ebp-8,然後壓棧ebp-4,然後呼叫函式input,最後input函式呼叫 結束後,利用esp+8恢復棧。由此可見,在c語言呼叫中預設的函式修飾_cdecl,由主呼叫函式進行引數壓棧並且恢復堆疊。

下面看一下:位址ebp-8和ebp-4是什麼?

在vc的view下選debug windows,然後選registers,顯示暫存器變數值,然後在選debug windows下面的memory,輸入ebp-8的值和ebp-4的值(或直接輸入ebp-8和-4),看一下這兩個位址實際儲存的是什麼值,實際上是變數 n 的位址(ebp-8),m的位址(ebp-4),由此可以看出:在主呼叫函式中進行實參的壓棧並且順序是從右到左。另外,由於實參是相應的變數的引用,也證明實際上引用傳遞的是變數的位址(類似指標)。

總結:在c或c++語言呼叫中預設的函式修飾_cdecl,由主呼叫函式進行引數壓棧並且恢復堆疊,實參的壓棧順序是從右到左,最後由主調函式進行堆疊恢復。由於主呼叫函式管理堆疊,所以可以實現變參函式。另外,命名修飾方法是在函式前加乙個下劃線(_).

2. winapi (實際上就是pascal,callback,_stdcall)

例子:

void winapi input( int &m,int &n);

看一下相應呼叫的彙編**:

00401068 lea eax,[ebp-8]

0040106b push eax

0040106c lea ecx,[ebp-4]

0040106f push ecx

00401070 call @ilt+5(input) (0040100a)

從以上呼叫input函式的過程可以看出:在呼叫此函式之前,首先壓棧ebp-8,然後壓棧ebp-4,然後呼叫函式input,在呼叫函式input之後,沒有相應的堆疊恢復工作(為其它的函式呼叫,所以我沒有列出)

下面再列出input函式本身的彙編**:(實際此函式不大,但做彙編例子還是大了些,大家可以只看前和後,中間**與此例子無關)

39: void winapi input( int &m,int &n)

40:

004011cc jmp input+18h (00401128)

61:62: }

004011d1 pop edi

004011d2 pop esi

004011d3 pop ebx

004011d4 add esp,48h

004011d7 cmp ebp,esp

004011d9 call __chkesp (004015b0)

004011de mov esp,ebp

004011e0 pop ebp

004011e1 ret 8

最後,我們看到在函式末尾部分,有ret 8,明顯是恢復堆疊,由於在32位c++中,變數位址為4個位元組(int也為4個位元組),所以彈棧兩個位址即8個位元組。

由此可以看出:在主呼叫函式中負責壓棧,在被呼叫函式中負責恢復堆疊。因此不能實現變參函式,因為被調函式不能事先知道彈棧數量,但在主調函式中是可以做到的,因為引數數量由主調函式確定。

下面再看一下,ebp-8和ebp-4這兩個位址實際儲存的是什麼值,ebp-8位址儲存的是n 的值,ebp -4儲存的是m的值。說明也是從右到左壓棧,進行引數傳遞。

總結:在主呼叫函式中負責壓棧,在被呼叫函式中負責彈出堆疊中的引數,並且負責恢復堆疊。因此不能實現變參函式,引數傳遞是從右到左。另外,命名修飾方法 是在函式前加乙個下劃線(_),在函式名後有符號(@),在@後面緊跟引數列表中的引數所佔位元組數(10進製),如:void input(int &m,int &n),被修飾成:_input@8

對於大多數api函式以及視窗訊息處理函式皆用 callback ,所以呼叫前,主調函式會先壓棧,然後api函式自己恢復堆疊。

如:

push edx

push edi

push eax

push ebx

call getdlgitemtexta

你可以想一下,這幾個暫存器中存的都是什麼?

參考:msdn

例子為在vc6.0下debug模式下的win32 console反彙編**。

VC 中的函式呼叫慣例

我們知道在進行函式呼叫時,有幾種呼叫方法,主要分為c式,pascal式.在c和c 中c式呼叫是預設的,類的成員函式預設呼叫為 stdcall。二者是有區別的,下面我們用例項說明一下 1.cdecl c和c 預設呼叫方式 例子 void input int m,int n 相當於void cdecl ...

函式呼叫慣例

函式的呼叫方與被呼叫方對於函式如何呼叫須要乙個明確的約定,這樣的約定就叫作呼叫慣例。乙個呼叫慣例一般會規定如下幾方面的內容 1.函式引數的傳遞順序及方式 函式引數的傳遞有多種方式,常見的是通過棧傳遞。函式的呼叫方將引數壓入棧中,函式自己再從棧中取出引數。對於有多個引數的函式,呼叫慣例需要約定函式呼叫...

棧與呼叫慣例

每個程序分配的記憶體由很多部分組成,通常稱為 段 1.文字段 包含了程序執行的程式二進位制機器語言指令,唯讀,可共享,因為多個程序可同時執行同一程式 2.初始化資料段 包含顯式初始化的全域性變數和靜態變數 3.未初始化資料段 也稱為bss段,包含未進行顯示初始化的全域性變數和靜態變數。為什麼分開放呢...