c++中的幾種函數調用約定(轉)


C++中的函數調用約定(調用慣例)主要針對三個問題:

1、參數傳遞的方式(是否采用寄存器傳遞參數、采用哪個寄存器傳遞參數、參數壓桟的順序等);

  參數的傳遞方式,最常見的是通過棧傳遞。函數的調用方將參數壓入棧中,函數自己再從棧中將參數取出。

  對於有多個參數的函數,調用慣例要規定函數調用方將參數壓棧的順序,是從左往右還是從右往左。有些調用慣例還允許使用寄存器傳遞參數。

2、函數調用結束后的棧指針由誰恢復(被調用的函數恢復還是調用者恢復);

  棧的維護方式:在函數將參數壓棧之后,函數體 會被調用,此后需要將被壓入的參數全部彈出,以使得棧在函數調用前后保持一致。這個彈出工作可以由函數的調用方來完成,也可以由函數本身完成。

3、函數編譯后的名稱;

  名稱修飾策略,為了鏈接的時候對調用慣例進行區分,調用慣例要對函數本身的名字進行修飾。不同的調用慣例有不同的名字修飾策略。

對實例代碼有幾點說明(使用的平台為vs2012+intel x86架構

1、棧頂指針即為esp;

2、int型占32字節內存;

3、桟頂為小地址端,棧底為大地址端,因此出棧需要增大esp;

下面對C++中見到的stdcall、cdecl、fastcall和thiscall做簡要說明。

1、stdcall

stdcall是standard call的縮寫,也被稱為pascal調用約定,因為pascal使用的函數調用約定就是stdcall。

使用stdcall的函數聲明方式為:int __stdcall function(int a,int b)

stdcall的調用約定意味着:

1)采用桟傳遞全部參數,參數從右向左壓入棧;

2)被調用函數負責恢復棧頂指針 ;

3)   函數名自動加前導的下划線,后面是函數名,之后緊跟一個@符號,其后緊跟着參數的尺寸,例如_function@4;

下面給出實例:

  1. int _stdcall funb(int p,int q)           //聲明為stdcall方式  
  2. {  
  3.     return p-q;  

 

  1. e=funb(3,4);  
  2. 012C42F7  push        4                   //參數q入棧  
  3. 012C42F9  push        3                   //參數p入棧       
  4. 012C42FB  call        funb (012C1244h)    //調用函數  
  5. 012C4300  mov         dword ptr [e],eax   //調用者沒有處理esp  

函數編譯后的匯編代碼為:

  1. int _stdcall funb(int p,int q)  
  2. {  
  3. 012C3D80  push        ebp    
  4. 012C3D81  mov         ebp,esp                //將esp保存入ebp中  
  5. 012C3D83  sub         esp,0C0h    
  6. 012C3D89  push        ebx                      
  7. 012C3D8A  push        esi    
  8. 012C3D8B  push        edi    
  9. 012C3D8C  lea         edi,[ebp-0C0h]    
  10. 012C3D92  mov         ecx,30h    
  11. 012C3D97  mov         eax,0CCCCCCCCh    
  12. 012C3D9C  rep stos    dword ptr es:[edi]    
  13.     return p-q;  
  14. 012C3D9E  mov         eax,dword ptr [p]    
  15. 012C3DA1  sub         eax,dword ptr [q]    
  16. }  

 

  1. 012C3DA4  pop         edi    
  2. 012C3DA5  pop         esi    
  3. 012C3DA6  pop         ebx    
  4. 012C3DA7  mov         esp,ebp    
  5. 012C3DA9  pop         ebp    
  6. 012C3DAA  ret         8    //注意此處,用被調函數負責恢復esp  

以上面函數為例,參數q首先被壓棧,然后是參數p(參數從右向左入棧),然后利用call調用函數,

而在編譯時,這個函數的名字被翻譯成_funb@8,其中8代表參數為8個字節(2個int型變量)。

另外,stdcall可以用於類成員函數的調用,這種情況下唯一的不同就是,所有參數從右向左依次入棧后,this指針會最后一個入棧。下面給出示例。

  1. class A  
  2. {  
  3. public:  
  4.     A(int a)  
  5.     {  
  6.         this->val=a;  
  7.     }  
  8.     int _stdcall fun(int par)  //類成員函數采用stdcall  
  9.     {  
  10.         return val-par;  
  11.     }  
  12. private:  
  13.     int val;  
  14. };  

函數調用代碼如下:

  1. A t(3);  
  2. int d,e,f,g;  
  3. g=t.fun(4);  

函數調用代碼編譯后為:

  1. g=t.fun(4);  
  2. 00DB4317  push        4                  //參數4入棧  
  3. 00DB4319  lea         eax,[t]    
  4. 00DB431C  push        eax                //this指針入棧,下面會驗證eax內容即為A的對象的地址  
  5. 00DB431D  call        A::fun (0DB1447h)    
  6. 00DB4322  mov         dword ptr [g],eax    

編譯后的代碼為:

  1. int _stdcall fun(int par)  
  2.     {  
  3. 00DB3CF0  push        ebp    
  4. 00DB3CF1  mov         ebp,esp    
  5. 00DB3CF3  sub         esp,0C0h    
  6. 00DB3CF9  push        ebx    
  7. 00DB3CFA  push        esi    
  8. 00DB3CFB  push        edi    
  9. 00DB3CFC  lea         edi,[ebp-0C0h]    
  10. 00DB3D02  mov         ecx,30h    
  11. 00DB3D07  mov         eax,0CCCCCCCCh    
  12. 00DB3D0C  rep stos    dword ptr es:[edi]    
  13.         return val-par;  
  14. 00DB3D0E  mov         eax,dword ptr [this]    
  15. 00DB3D11  mov         eax,dword ptr [eax]    
  16. 00DB3D13  sub         eax,dword ptr [par]    
  17.     }  
  18. 00DB3D16  pop         edi    
  19. 00DB3D17  pop         esi    
  20. 00DB3D18  pop         ebx    
  21. 00DB3D19  mov         esp,ebp    
  22. 00DB3D1B  pop         ebp    
  23. 00DB3D1C  ret         8          //由被調用函數負責恢復棧頂指針,由於參數為int型變量(4字節)和一個指針(32為,4字節),共8字節  

下面驗證入棧時eax中的內容為A對象的地址。

入棧時eax內容如下,為0x0035F808。

找到內存中0x0035F808的內容,為3,。

再看main函數中實例化對象的代碼。

可見,this指針正是通過eax入棧。

由此可見,用於類成員函數時,唯一的不同就是在參數入棧完畢后,this指針會最后一個入棧。

2、cdecl

 

 

cdecl是C Declaration的縮寫,又稱為C調用約定,是C語言缺省的調用約定,采用這種方式調用的函數的聲明是:

int function (int a ,int b)                   //不加修飾就是采用默認的C調用約定

int _cdecl function(int a,int b)         //明確指出采用C調用約定

cdecl調用方式規定:

1、采用桟傳遞參數,參數從右向左依次入棧;

2、由調用者負責恢復棧頂指針;

3、在函數名前加上一個下划線前綴,格式為_function;

要注意的是,調用參數個數可變的函數只能采用這種方式(如printf)。

下面給出實例。

  1. int _cdecl funa(int p,int q)        //采用cdecl方式  
  2. {  
  3.     return p-q;  
  4. }  

調用處的代碼編譯為:

  1. d=funa(3,4);  
  2. 012C42E8  push        4    
  3. 012C42EA  push        3    
  4. 012C42EC  call        funa (012C1064h)       //調用funca  
  5. 012C42F1  add         esp,8                  //調用者恢復棧頂指針esp  
  6. 012C42F4  mov         dword ptr [d],eax      //返回值傳遞給變量d  

函數編譯后的代碼為:

  1. int _cdecl funa(int p,int q)  
  2. {  
  3. 012C3D40  push        ebp    
  4. 012C3D41  mov         ebp,esp    
  5. 012C3D43  sub         esp,0C0h    
  6. 012C3D49  push        ebx    
  7. 012C3D4A  push        esi    
  8. 012C3D4B  push        edi    
  9. 012C3D4C  lea         edi,[ebp-0C0h]    
  10. 012C3D52  mov         ecx,30h    
  11. 012C3D57  mov         eax,0CCCCCCCCh    
  12. 012C3D5C  rep stos    dword ptr es:[edi]    
  13.     return p-q;  
  14. 012C3D5E  mov         eax,dword ptr [p]    
  15. 012C3D61  sub         eax,dword ptr [q]    
  16. }  
  17. 012C3D64  pop         edi    
  18. 012C3D65  pop         esi    
  19. 012C3D66  pop         ebx    
  20. 012C3D67  mov         esp,ebp    
  21. 012C3D69  pop         ebp    
  22. 012C3D6A  ret                   //注意此處,被調函數沒有恢復esp  

因此,stdcall與cdecl的區別就是誰負責恢復棧頂指針和編譯后函數的名稱問題。

cedcal同樣可以用於類成員函數的調用。此時,cdedl與stdcall的區別在於由誰恢復棧頂指針。

類定義如下:

  1. class A  
  2. {  
  3. public:  
  4.     A(int a)  
  5.     {  
  6.         this->val=a;  
  7.     }  
  8.     int _cdecl fun(int par)       //采用cedcl方式  
  9.     {  
  10.         return val-par;  
  11.     }  
  12. private:  
  13.     int val;  
  14. };  

調用代碼編譯如下:

 
  1.     g=t.fun(4)  
  2. 013D4317  push        4    
  3. 013D4319  lea         eax,[t]    
  4. 013D431C  push        eax                     //先入棧參數4,后入棧this指針  
  5. 013D431D  call        A::fun (013D144Ch)    
  6. 013D4322  add         esp,8                   //由調用者恢復棧頂指針  
  7. 013D4325  mov         dword ptr [g],eax     

3、fastcall

采用fasecall的函數聲明方式為:

int __fastcall function(int a,int b)

fastcall調用約定意味着:
1、函數的第一個和第二個(從左向右)32字節參數(或者尺寸更小的)通過ecx和edx傳遞,其他參數通過桟傳遞。從第三個參數(如果有的話)開始從右向左的順序壓棧;
2、被調用函數恢復棧頂指針;
3、在函數名之前加上"@",在函數名后面也加上“@”和參數字節數,例如@function@8;

示例代碼如下:

  1. int __fastcall func(int p,int q,int r)    //采用fastcall  
  2. {  
  3.     return p-q-r;  
  4. }  

調用代碼如下:

  1. f=func(3,4,5);  
  2. 00E74303  push        5           //第三個參數r壓桟  
  3. 00E74305  mov         edx,4       //p q通過ecx和edx傳遞  
  4. 00E7430A  mov         ecx,3    
  5. 00E7430F  call        func (0E71442h)    
  6. 00E74314  mov         dword ptr [f],eax    //調用者不負責恢復棧頂指針esp  

函數編譯后的代碼如下:

  1. int __fastcall func(int p,int q,int r)  
  2. {  
  3. 00E73DC0  push        ebp    
  4. 00E73DC1  mov         ebp,esp    
  5. 00E73DC3  sub         esp,0D8h    
  6. 00E73DC9  push        ebx    
  7. 00E73DCA  push        esi    
  8. 00E73DCB  push        edi    
  9. 00E73DCC  push        ecx    
  10. 00E73DCD  lea         edi,[ebp-0D8h]    
  11. 00E73DD3  mov         ecx,36h    
  12. 00E73DD8  mov         eax,0CCCCCCCCh    
  13. 00E73DDD  rep stos    dword ptr es:[edi]    
  14. 00E73DDF  pop         ecx    
  15. 00E73DE0  mov         dword ptr [q],edx    
  16. 00E73DE3  mov         dword ptr [p],ecx    
  17.     return p-q-r;  
  18. 00E73DE6  mov         eax,dword ptr [p]    
  19. 00E73DE9  sub         eax,dword ptr [q]    
  20. 00E73DEC  sub         eax,dword ptr [r]    
  21. }  
  22. 00E73DEF  pop         edi    
  23. 00E73DF0  pop         esi    
  24. 00E73DF1  pop         ebx    
  25. 00E73DF2  mov         esp,ebp    
  26. 00E73DF4  pop         ebp    
  27. }  
  28. 00E73DF5  ret         4     //恢復棧頂指針,由於只有一個參數r被壓桟,因此esp+4即可  

可以看到,fasecall利用寄存器ecx與edx傳遞參數,避免了訪存帶來的開銷。適合少量參數提高效率的場合。

4、thiscall

thiscall是唯一一個不能明確指明的函數修飾,因為thiscall只能用於C++類成員函數的調用,同時thiscall也是C++成員函數缺省的調用約定。由於成員函數調用還有一個this指針,因此必須特殊處理。

thiscall意味着:

1、采用桟傳遞參數,參數從右向左入棧。如果參數個數確定,this指針通過ecx傳遞給被調用者;如果參數個數不確定,this指針
在所有參數壓棧后被壓入堆棧;
2、對參數個數不定的,調用者清理堆棧,否則由被調函數清理堆棧


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM