宏WINAPI和幾種調用約定


在VC SDK的WinDef.h中,宏WINAPI被定義為__stdcall,這是C語言中一種調用約定,常用的還有__cdecl__fastcall。這些調用約定會對我們的代碼產生什么樣的影響?讓我們逐個分析。

首先,在x86平台上,用VC編譯這樣一段代碼:

 1 int __cdecl TestC(int n0, int n1, int n2, int n3, int n4, int n5)
 2 {
 3     int n = n0 + n1 + n2 + n3 + n4 + n5;
 4     return n;
 5 }
 6 
 7 int __stdcall TestStd(int n0, int n1, int n2, int n3, int n4, int n5)
 8 {
 9     int n = n0 + n1 + n2 + n3 + n4 + n5;
10     return n;
11 }
12 
13 int __fastcall TestFast(int n0, int n1, int n2, int n3, int n4, int n5)
14 {
15     int n = n0 + n1 + n2 + n3 + n4 + n5;
16     return n;
17 }
18 
19 int _tmain(int argc, _TCHAR* argv[])
20 {
21     TestC(0, 1, 2, 3, 4, 5);
22     TestStd(0, 1, 2, 3, 4, 5);
23     TestFast(0, 1, 2, 3, 4, 5);
24     return 0;
25 }

然后在main函數的開始出設置斷點、開始調試。

首先,我們會看到編譯器為__cdecl產生的匯編代碼:

;main函數中的調用代碼
TestC(0, 1, 2, 3, 4, 5); 013F243E push 5 013F2440 push 4 013F2442 push 3 013F2444 push 2 013F2446 push 1 013F2448 push 0 013F244A call TestC (13F11D1h) 013F244F add esp,18h

;TestC函數的實現,省略無關代碼
int __cdecl TestC(int n0, int n1, int n2, int n3, int n4, int n5) { 013F1400 push ebp 013F1401 mov ebp,esp 013F1403 ...
...
013F1439 mov esp,ebp 013F143B pop ebp 013F143C ret

由以上代碼可以發現,main函數中調用TestC函數時,將6個參數由右至左依次壓棧,也就是全部參數都通過棧傳遞。在TestC函數ret時,並沒有清理棧上的參數,而是在main函數中通過調整esp來清理的。正因為如此,使得__cdecl可以支持參數個數不定的函數調用,如 :

void f(char* fmt, ...);

再來看一下__stdcall的匯編代碼:

;main函數中的調用代碼
TestStd(0, 1, 2, 3, 4, 5); 00FB2452 push 5 00FB2454 push 4 00FB2456 push 3 00FB2458 push 2 00FB245A push 1 00FB245C push 0 00FB245E call TestStd (0FB11E0h) ;TestStd函數的實現,省略無關代碼 int __stdcall TestStd(int n0, int n1, int n2, int n3, int n4, int n5) { 00FB1840 push ebp 00FB1841 mov ebp,esp 00FB1843 ...
...

00FB1879 mov esp,ebp 00FB187B pop ebp 00FB187C ret 18h

以上代碼中,main函數中調用TestStd函數時,將6個參數由右至左依次壓棧,這一點與__cdecl相同。不同的是在TestStd函數ret時,清理掉了棧上的6個參數(18h = 4 * 6)。

最后看一下__fastcall產生的代碼:

;main函數中的調用代碼
TestFast(0, 1, 2, 3, 4, 5); 00FB2463 push 5 00FB2465 push 4 00FB2467 push 3 00FB2469 push 2 00FB246B mov edx,1 00FB2470 xor ecx,ecx 00FB2472 call TestFast (00FB11E5)
;TestFast函數的實現,省略無關代碼 int __fastcall TestFast(int n0, int n1, int n2, int n3, int n4, int n5) { 00FB1880 push ebp 00FB1881 mov ebp,esp 00FB1883 ...
...

00FB18C1 mov esp,ebp 00FB18C3 pop ebp 00FB18C4 ret 10h

與以上兩個調用約定顯著不同的是,__fastcall使用ecx和edx來傳遞前兩個參數(如果有的話),剩余的參數依然按照從右到左的順序壓棧傳遞。並且在函數ret時,類似於__stdcall,會清理通過棧傳遞的參數(此處為4個,10h = 4 * 4)。

接下來看一下x64平台上產生的代碼:

;main函數中的調用代碼
000000013F3111A0
...
...
000000013F3111AA sub rsp,30h 000000013F3111AE ...
... TestC(
0, 1, 2, 3, 4, 5); 000000013F3111C1 mov dword ptr [rsp+28h],5 000000013F3111C9 mov dword ptr [rsp+20h],4 000000013F3111D1 mov r9d,3 000000013F3111D7 mov r8d,2 000000013F3111DD mov edx,1 000000013F3111E2 xor ecx,ecx 000000013F3111E4 call TestC (13F31100Ah) TestStd(0, 1, 2, 3, 4, 5); 000000013F3111E9 mov dword ptr [rsp+28h],5 000000013F3111F1 mov dword ptr [rsp+20h],4 000000013F3111F9 mov r9d,3 000000013F3111FF mov r8d,2 000000013F311205 mov edx,1 000000013F31120A xor ecx,ecx 000000013F31120C call TestStd (13F311019h) TestFast(0, 1, 2, 3, 4, 5); 000000013F311211 mov dword ptr [rsp+28h],5 000000013F311219 mov dword ptr [rsp+20h],4 000000013F311221 mov r9d,3 000000013F311227 mov r8d,2 000000013F31122D mov edx,1 000000013F311232 xor ecx,ecx 000000013F311234 call TestFast (13F31101Eh)
000000013F311239  ...
...

000000013F31123B  add         rsp,30h
000000013F31123F  ...
...
;TestC函數的實現,省略無關代碼
int __cdecl TestC(int n0, int n1, int n2, int n3, int n4, int n5) { 000000013F311080 mov dword ptr [rsp+20h],r9d 000000013F311085 mov dword ptr [rsp+18h],r8d 000000013F31108A mov dword ptr [rsp+10h],edx 000000013F31108E mov dword ptr [rsp+8],ecx 000000013F311092 ...
...
000000013F3110D1 ret
;TestStd函數的實現,省略無關代碼 int __stdcall TestStd(int n0, int n1, int n2, int n3, int n4, int n5) { 000000013F3110E0 mov dword ptr [rsp+20h],r9d 000000013F3110E5 mov dword ptr [rsp+18h],r8d 000000013F3110EA mov dword ptr [rsp+10h],edx 000000013F3110EE mov dword ptr [rsp+8],ecx 000000013F3110F2 ...
...
000000013F311131 ret
;TestFast函數的實現,省略無關代碼 int __fastcall TestFast(int n0, int n1, int n2, int n3, int n4, int n5) { 000000013F311140 mov dword ptr [rsp+20h],r9d 000000013F311145 mov dword ptr [rsp+18h],r8d 000000013F31114A mov dword ptr [rsp+10h],edx 000000013F31114E mov dword ptr [rsp+8],ecx 000000013F311152 ...
...
000000013F311191 ret

可以看到,編譯器忽略了3個不同的調用約定keyword,而為它們產生了同樣的代碼:調用者使用rcx/ecx、rdx/edx、r8/r8d、r9/r9d來傳遞前4個參數,剩余的參數通過棧傳遞,這有些類似於x86下的__fastcall,不同的是,棧上保留了前4個參數的存儲空間。而且類似於x86下的__cdecl,函數ret時不會清理棧,棧的平衡由調用者負責。

在Debug版的代碼中,TestXXX函數的開始處,首先將rcx/ecx、rdx/edx、r8/r8d、r9/r9d中的值拷貝到棧上預留的空間里,應該是為了方便調試。在Release版中,這些預留空間有時被用來備份某個通用寄存器的值。

x64下的這種調用約定,像是__fastcall__cdecl的一個結合,既提高了性能又能支持不定個數的參數。

調用約定是代碼函數化、模塊化的基礎,其實就是一種參數傳遞、棧平衡的策略。我們在代碼中使用一個函數時,只需要提供函數聲明,編譯器就可以依照約定產生出調用這個函數的機器碼,而在被調用的函數中,也是按照約定知道參數如何傳遞過來及如何使用。


免責聲明!

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



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