1. 程式人生 > >c++中的幾種函數調用約定(轉)

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

standard amp 可見 代碼 return ext 只有一個 highlight 定義

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;
  4. }

  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、對參數個數不定的,調用者清理堆棧,否則由被調函數清理堆棧

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