天天看點

_stdcall與_cdecl的差別

1、_stdcall是Pascal程式的預設調用方式,通常用于Win32 Api中,函數采用從右到左的壓棧方式,自己在退出時清空堆棧。VC将函數編譯後會在函數名前面加上下劃線字首,在函數名後加上"@"和參數的位元組數。 int f(void *p) -->> _f@4(在外部彙編語言裡可以用這個名字引用這個函數)

2、C調用約定(即用__cdecl關鍵字說明)(The C default calling convention)按從右至左的順序壓參數入棧,由調用者把參數彈出棧。對于傳送參數的記憶體棧是由調用者來維護的(正因為如此,實作可變參數 vararg的函數(如printf)隻能使用該調用約定)。另外,在函數名修飾約定方面也有所不同。 _cdecl是C和C++程式的預設調用方式。每一個調用它的函數都包含清空堆棧的代碼,是以産生的可執行檔案大小會比調用_stdcall函數的大。函 數采用從右到左的壓棧方式。VC将函數編譯後會在函數名前面加上下劃線字首。

[下面是轉的,原文不知道在哪。。。。] 

我們知道在進行函數調用時,有幾種調用方法,分為C式,Pascal式。在C和C++中C式調用是預設的,除非特殊聲明。二者是有差別的,下面我們用執行個體說明一下:  

  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:       {   

  00401110       push                 ebp   

  00401111       mov                   ebp,esp   

  00401113       sub                   esp,48h   

  00401116       push                 ebx   

  00401117       push                 esi   

  00401118       push                 edi   

  00401119       lea                   edi,[ebp-48h]   

  0040111C       mov                   ecx,12h   

  00401121       mov                   eax,0CCCCCCCCh   

  00401126       rep   stos         dword   ptr   [edi]   

  41:               int   s,i;   

  42:   

  43:               while(1)   

  00401128       mov                   eax,1   

  0040112D       test                 eax,eax   

  0040112F       je                     Input+0C1h   (004011d1)   

  44:               {   

  45:               printf(""nPlease   input   the   first   number   m:");   

  00401135       push                 offset   string   ""nPlease   input   the   first   number   m"...   (004260b8)   

  0040113A       call                 printf   (00401530)   

  0040113F       add                   esp,4   

  46:               scanf("%d",&m);   

  00401142       mov                   ecx,dword   ptr   [ebp+8]   

  00401145       push                 ecx   

  00401146       push                 offset   string   "%d"   (004260b4)   

  0040114B       call                 scanf   (004015f0)   

  00401150       add                   esp,8   

  47:   

  48:               if   (   m<1   )   continue;   

  00401153       mov                   edx,dword   ptr   [ebp+8]   

  00401156       cmp                   dword   ptr   [edx],1   

  00401159       jge                   Input+4Dh   (0040115d)   

  0040115B       jmp                   Input+18h   (00401128)   

  49:               printf(""nPlease   input   the   first   number   n:");   

  0040115D       push                 offset   string   ""nPlease   input   the   first   number   n"...   (0042608c)   

  00401162       call                 printf   (00401530)   

  00401167       add                   esp,4   

  50:               scanf("%d",&n);   

  0040116A       mov                   eax,dword   ptr   [ebp+0Ch]   

  0040116D       push                 eax   

  0040116E       push                 offset   string   "%d"   (004260b4)   

  00401173       call                 scanf   (004015f0)   

  00401178       add                   esp,8   

  51:   

  52:               if   (   n<1   )   continue;   

  0040117B       mov                   ecx,dword   ptr   [ebp+0Ch]   

  0040117E       cmp                   dword   ptr   [ecx],1   

  00401181       jge                   Input+75h   (00401185)   

  00401183       jmp                   Input+18h   (00401128)   

  53:   

  54:               for(i=1,s=0;i<=n;i++)   

  00401185       mov                   dword   ptr   [ebp-8],1   

  0040118C       mov                   dword   ptr   [ebp-4],0   

  00401193       jmp                   Input+8Eh   (0040119e)   

  00401195       mov                   edx,dword   ptr   [ebp-8]   

  00401198       add                   edx,1   

  0040119B       mov                   dword   ptr   [ebp-8],edx   

  0040119E       mov                   eax,dword   ptr   [ebp+0Ch]   

  004011A1       mov                   ecx,dword   ptr   [ebp-8]   

  004011A4       cmp                   ecx,dword   ptr   [eax]   

  004011A6       jg                     Input+0A3h   (004011b3)   

  55:                       s=s+i;   

  004011A8       mov                   edx,dword   ptr   [ebp-4]   

  004011AB       add                   edx,dword   ptr   [ebp-8]   

  004011AE       mov                   dword   ptr   [ebp-4],edx     

  004011B1       jmp                   Input+85h   (00401195)   

  56:               if   (   m   >=   s   )   

  004011B3       mov                   eax,dword   ptr   [ebp+8]   

  004011B6       mov                   ecx,dword   ptr   [eax]   

  004011B8       cmp                   ecx,dword   ptr   [ebp-4]   

  004011BB       jl                     Input+0AFh   (004011bf)   

  57:                       break;   

  004011BD       jmp                   Input+0C1h   (004011d1)   

  58:               else   

  59:                       printf("   m   <   n*(n+1)/2,Please   input   again!"n");   

  004011BF       push                 offset   string   "   m   <   n*(n+1)/2,Please   input   agai"...   (00426060)   

  004011C4       call                 printf   (00401530)   

  004011C9       add                   esp,4   

  60:               }   

  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函數自己恢複堆棧。  

本文轉自CoderZh部落格園部落格,原文連結:http://www.cnblogs.com/coderzh/archive/2008/12/01/1345053.html,如需轉載請自行聯系原作者

繼續閱讀