幾種類型
約束事件
參數傳遞順序
調用堆疊清理
1.調用者清除棧。
常用描述
__cdecl
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],0c = sumExample(2, 3);0040108F 6A 03 push 300401091 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 ebp00401031 8B EC mov ebp,esp00401033 83 EC 40 sub esp,40h 為局部變數預留空間00401036 53 push ebx00401037 56 push esi00401038 57 push edi00401039 8D 7D C0 lea edi,[ebp-40h]0040103C B9 10 00 00 00 mov ecx,10h00401041 B8 CC CC CC CC mov eax,0CCCCCCCCh00401046 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 edi0040104F 5E pop esi00401050 5B pop ebx00401051 8B E5 mov esp,ebp00401053 5D pop ebp00401054 C3 ret
這裡就是函式收尾,也就是function epilog
經過上面的分析,相信你對
__cdecl調用約定有了比較清晰的認識了。但是這裡我們應該想想為什麼不在被調函式內進行
堆疊平衡呢?在這裡我們應該要考慮類似於像
scanf和
printf這樣的函式,這裡我們應該明白這兩個函式的參數都是可變的,如果參數不固定的話,在
被調用函式內就無法知道參數究竟使用了多少個
位元組,所以為了實現可變參數,我們必須要在被調函式執行之後我們才知道參數究竟用了多少位元組,所以我們在調用者來進行堆疊平衡操作。在後面我們將要對printf函式內部是怎么實現做一些探究。
__stdcall
還是與上面一樣,我們在函式的面前用__stdcall作為修飾符。此時函式將會採用__stdcall調用約定
int __stdcallsumExample (int a, int b);
__stdcall調用約定的主要特徵是:
3、在函式名的前面用下劃線修飾,在函式名的後面由@來修飾並加上
棧需要的位元組數的空間(_sumExample@8)。
這兩個push可以說明函式的參數是由右向左傳遞的。
call _sumExample@8 //調用函式mov dword ptr [c], eax //eax暫存器保存函式的返回值,此時將返回值賦值給局部變數c。
再來看看函式的代碼。
mov eax, dword ptr [a]add eax, dword ptr [b]
函式的收尾也是和__cdecl調用約定是相同的
ret 8 //兩個4位元組的參數
上面的是文章本來的說明,但在
VC中卻好像有點區別。
0040108F 6A 03 push 300401091 6A 02 push 200401093 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類型總是通過堆疊來傳遞的。
#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],016: double d = 0.0;004010CF C7 45 F4 00 00 00 00 mov dword ptr [ebp-0Ch],0004010D6 C7 45 F8 00 00 00 00 mov dword ptr [ebp-8],017: c = sumExample(2, 3, 5);004010DD 6A 05 push 5004010DF BA 03 00 00 00 mov edx,3004010E4 B9 02 00 00 00 mov ecx,2004010E9 E8 26 FF FF FF call @ILT+15(@sumExample@8) (00401014)004010EE 89 45 FC mov dword ptr [ebp-4],eax18:19: d = sumExampled(2.3, 2.5);004010F1 68 00 00 04 40 push 40040000h004010F6 6A 00 push 0004010F8 68 66 66 02 40 push 40026666h004010FD 68 66 66 66 66 push 66666666h00401102 E8 FE FE FF FF call @ILT+0(@sumExampled@16) (00401005)00401107 DD 5D F4 fstp qwordptr [ebp-0Ch]