複製鏈接
請複製以下鏈接發送給好友

函數調用約定

鎖定
函數調用約定,是指當一個函數被調用時,函數的參數會被傳遞給被調用的函數和返回值會被返回給調用函數。函數的調用約定就是描述參數是怎麼傳遞和由誰平衡堆棧的,當然還有返回值。
中文名
函數調用約定
外文名
Function calling convention
幾種類型
__stdcall,__cdecl,__fastcall
調用堆棧清理
調用者清除棧
參數傳遞順序
從右到左依次入棧

函數調用約定幾種類型

__stdcall,__cdecl,__fastcall,__thiscall,__nakedcall,__pascal,__vectorcall

函數調用約定約束事件

函數調用約定參數傳遞順序

1.從右到左依次入棧:__stdcall,__cdecl,__thiscall,__fastcall
2.從左到右依次入棧:__pascal

函數調用約定調用堆棧清理

1.調用者清除棧。
2.被調用函數返回後清除棧。

函數調用約定常用描述

__cdecl
1、參數是從右向左傳遞的,也是放在堆棧中。
2、堆棧平衡是由調用函數來執行的(在call B,之後會有add esp x,x表示參數的字節數)。
3、函數的前面會加一個前綴_(_sumExample)
下面來看看具體的反彙編代碼,這是從VC反彙編的代碼截取的一部分代碼。
int c = 0;
00401088 C7 45 FC 00 00 00 00 mov dword ptr [ebp-4],0
c = sumExample(2, 3);
0040108F 6A 03 push 3
00401091 6A 02 push 2
從上面的兩個push操作我們就可以知道參數是從右向左傳遞的了。另外這裏也回答前面的問題為什麼參數會被擴展為4個字節,因為堆棧的操作都是對一個字進行操作的,所以參數都是4個字節的。
00401093 E8 7C FF FF FF call @ILT+15(_Max) (00401014)
這裏就是調用函數操作了。在進行call操作之後,會自動將call的下一條語句作為函數的返回地址保存在棧中,也就是下面的(00401098)。在地址00401014處我們可以看到這樣的一小段代碼
@ILT+0(_Max):
00401005 E9 26 00 00 00 jmp _sumExample (00401030)
這裏就可以知道程序在編譯之後會在函數前面加上前綴_
00401098 83 C4 08 add esp,8
這裏就是平衡堆棧操作了。可以看出是在調用者進行的。
0040109B 89 45 FC mov dword ptr [ebp-4],eax
保存值是由eax寄存器返回的,從這裏就可以看出了。
return 0;
0040109E 33 C0 xor eax,eax
 }
int __cdecl sumExample(int a, int b)
{
00401030 55 push ebp
00401031 8B EC mov ebp,esp
00401033 83 EC 40 sub esp,40h 為局部變量預留空間
00401036 53 push ebx
00401037 56 push esi
00401038 57 push edi
00401039 8D 7D C0 lea edi,[ebp-40h]
0040103C B9 10 00 00 00 mov ecx,10h
00401041 B8 CC CC CC CC mov eax,0CCCCCCCCh
00401046 F3 AB rep stos dword ptr [edi]
這上面的一段代碼就是函數的開端了。也就是function prolog。通過將一些寄存器來對它們進行保存,也就像中斷髮生後,需要保護現場一樣。
return (a + b);
00401048 8B 45 08 mov eax,dword ptr [ebp+8]
0040104B 03 45 0C add eax,dword ptr [ebp+0Ch]
}
0040104E 5F pop edi
0040104F 5E pop esi
00401050 5B pop ebx
00401051 8B E5 mov esp,ebp
00401053 5D pop ebp
00401054 C3 ret
這裏就是函數收尾,也就是function epilog
經過上面的分析,相信你對__cdecl調用約定有了比較清晰的認識了。但是這裏我們應該想想為什麼不在被調函數內進行堆棧平衡呢?在這裏我們應該要考慮類似於像scanfprintf這樣的函數,這裏我們應該明白這兩個函數的參數都是可變的,如果參數不固定的話,在被調用函數內就無法知道參數究竟使用了多少個字節,所以為了實現可變參數,我們必須要在被調函數執行之後我們才知道參數究竟用了多少字節,所以我們在調用者來進行堆棧平衡操作。在後面我們將要對printf函數內部是怎麼實現做一些探究。
__stdcall
Win32 API函數絕大部分都是採用__stdcall調用約定的。WINAPI其實也只是__stdcall的一個別名而已。
#define WINAPI __stdcall
還是與上面一樣,我們在函數的面前用__stdcall作為修飾符。此時函數將會採用__stdcall調用約定
int __stdcallsumExample (int a, int b);
__stdcall調用約定的主要特徵是:
1、參數是從右往左傳遞的,也是放在堆棧中。
2、函數的堆棧平衡操作是由被調用函數執行的。
3、在函數名的前面用下劃線修飾,在函數名的後面由@來修飾並加上需要的字節數的空間(_sumExample@8)。
push 3
push 2
這兩個push可以説明函數的參數是由右向左傳遞的。
call _sumExample@8 //調用函數
mov dword ptr [c], eax //eax寄存器保存函數的返回值,此時將返回值賦值給局部變量c。
再來看看函數的代碼。
函數的開端與__cdecl調用約定是相同的
mov eax, dword ptr [a]
add eax, dword ptr [b]
函數的收尾也是和__cdecl調用約定是相同的
另外在最後面將對堆棧進行平衡操作。
ret 8 //兩個4字節的參數
上面的是文章本來的説明,但在VC中卻好像有點區別。
0040108F 6A 03 push 3
00401091 6A 02 push 2
00401093 E8 81 FF FF FF call @ILT+20(_sumExample) (00401019)
FC mov dword ptr [ebp-4],eax
sumExample函數
return (a + b);
00401048 8B 45 08 mov eax,dword ptr [ebp+8]
0040104B 03 45 0C add eax,dword ptr [ebp+0Ch]
00401054 C2 08 00 ret 8 //堆棧平衡操作
因為棧的清理(堆棧平衡操作)是由被調用函數執行的。所以使用__stdcall調用約定生成的可執行文件要比__cdecl的要小,因為在每次的函數調用都要產生堆棧清理的代碼。函數具有可變參數像我wsprintf這個函數,與前面的prinf一樣,都必須使用__cdecl調用約定,因為只有調用者才知道參數的數量在每一次的函數調用,因此也只有調用者才能夠執行堆棧清理操作。
__fastcall
__fastcall見名知其意,其特點就是快。__fastcall函數調用約定表明了參數應該放在寄存器中,而不是在棧中,VC編譯器採用調用約定傳遞參數時,最左邊的兩個不大於4個字節(DWORD)的參數分別放在ecx和edx寄存器。當寄存器用完的時候,其餘參數仍然從右到左的順序壓入堆棧。像浮點值、遠指針和__int64類型總是通過堆棧來傳遞的。 [1] 
下面來看看使用測試的源代碼
#include <stdio.h>
int __fastcall sumExample(int a, int b, int c)
{
return (a + b + c);
}
double __fastcall sumExampled(double a, double b)
{
return (a + b);
}
int main()
{
int c = 0;
double d = 0.0;
c = sumExample(2, 3, 5);
d = sumExampled(2.3, 2.5);
return 0;
}
15: int c = 0;
004010C8 C7 45 FC 00 00 00 00 mov dword ptr [ebp-4],0
16: double d = 0.0;
004010CF C7 45 F4 00 00 00 00 mov dword ptr [ebp-0Ch],0
004010D6 C7 45 F8 00 00 00 00 mov dword ptr [ebp-8],0
17: c = sumExample(2, 3, 5);
004010DD 6A 05 push 5
004010DF BA 03 00 00 00 mov edx,3
004010E4 B9 02 00 00 00 mov ecx,2
004010E9 E8 26 FF FF FF call @ILT+15(@sumExample@8) (00401014)
004010EE 89 45 FC mov dword ptr [ebp-4],eax
18:
19: d = sumExampled(2.3, 2.5);
004010F1 68 00 00 04 40 push 40040000h
004010F6 6A 00 push 0
004010F8 68 66 66 02 40 push 40026666h
004010FD 68 66 66 66 66 push 66666666h
00401102 E8 FE FE FF FF call @ILT+0(@sumExampled@16) (00401005)
00401107 DD 5D F4 fstp qwordptr [ebp-0Ch]
參考資料
  • 1.    伍建全, 何宗琦. 函數調用約定淺析[J]. 重慶科技學院學報:自然科學版, 2006, 8(2):82-84.