我們知道在進行函數調用時,有幾種調用方法,主要分爲C式,Pascal式.在C和C++中C式調用是缺省的,類的成員函數缺省調用爲_stdcall。二者是有區別的,下面我們用實例說明一下: 1. __cdecl :C和C++缺省調用方式 例子: |
void Input( int &m,int &n);/*相當於void __cdecl Input(int &m,int &n);*/ |
從以上調用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); |
從以上調用Input函數的過程可以看出:在調用此函數之前,首先壓棧ebp-8,然後壓棧ebp-4,然後調用函數Input,在調用函數Input之後,沒有相應的堆棧恢復工作(爲其它的函數調用,所以我沒有列出) 下面再列出Input函數本身的彙編代碼:(實際此函數不大,但做彙編例子還是大了些,大家可以只看前和後,中間代碼與此例子無關) |
39: void WINAPI Input( int &m,int &n) |
最後,我們看到在函數末尾部分,有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 |
你可以想一下,這幾個寄存器中存的都是什麼? 參考:msdn 例子爲在VC6.0下debug模式下的Win32 Console反彙編代碼。 |
VC++中的函數調用慣例
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.