現代的編程語言的函數竟然有那麼多的調用方式。這些東西要完全理解還得通過匯編代碼才好理解。他們各自有自己的特點
其實這些調用方式的差別在主要在一下幾個方面
1.參數處理方式(傳遞順序,存取(利用盞還是寄存器))
2.函數的結尾處理方式(善后處理)
以下是理論:
__cdecl 由調用者平棧,參數從右到左依次入棧 是C和C++程序的缺省調用方式。每一個調用它的函數都包含清空堆棧的代碼,
所以產生的可執行文件大小會比調用_stdcall函數的大。函數采用從右到左的壓棧方式。VC將函數編譯后會在函數名前面加上
下劃線前綴。是MFC缺省調用約定
__stdcall ,WINAPI,CALLBACK ,PASCAL 由被調用者平棧,參數從右到左依次入棧 ._stdcall是Pascal程序的缺省調用方式,
通常用于Win32 Api中,函數采用從右到左的壓棧方式,自己在退出時清空堆棧。VC將函數編譯后會在函數名前面加上下劃
線前綴,在函數名后加上"@"和參數的字節數
__fastcall 由被調用者平棧,參數先賦值給寄存器,然后入棧 “人”如其名,它的主要特點就是快,因為它是通過寄存器來傳送參數的
(實際上,它用ECX和EDX傳送前兩個雙字(DWORD)或更小的參數,剩下的參數仍舊自右向左壓棧傳送,被調用的函數在返回前
清理傳送參數的內存棧),在函數名修飾約定方面,它和前兩者均不同.
_fastcall方式的函數采用寄存器傳遞參數,VC將函數編譯后會在函數名前面加上"@"前綴,在函數名后加上"@"和參數的字節數。
__thiscall 由被調用者平棧,參數入棧,this 指針賦給 ecx 寄存器 僅僅應用于“C++”成員函數。this指針存放于CX寄存器,參數從右
到左壓。thiscall不是關鍵詞,因此不能被程序員指定。
__declspec(naked) 這是一個很少見的調用約定,一般程序設計者建議不要使用。編譯器不會給這種函數增加初始化和清理代碼,
更特殊的是,你不能用return返回返回值,只能用插入匯編返回結果。這一般用于實模式驅動程序設計.
以下是實踐:
int __stdcall test_stdcall(char para1, char para2)
{
para1 = para2;
return 0;
}
int __cdecl test_cdecl(char para,
)
{
char p = '\n';
va_list marker;
va_start( marker, para );
while( p != '\0' )
{
p = va_arg( marker, char);
printf("%c\n", p);
}
va_end( marker );
return 0;
}
int pascal test_pascal(char para1, char para2)
{
return 0;
}
int __fastcall test_fastcall(char para1, char para2, char para3, char para4)
{
para1 = (char)1;
para2 = (char)2;
para3 = (char)3;
para4 = (char)4;
return 0;
}
__declspec(naked) void __stdcall test_naked(char para1, char para2)
{
__asm
{
push ebp
mov ebp, esp
push eax
mov al,byte ptr [ebp + 0Ch]
xchg byte ptr [ebp + 8],al
pop eax
pop ebp
ret 8
}
// return ;
}
int main(int argc, char* argv[])
{
test_stdcall( 'a', 'b' );
test_cdecl( 'c','d','e','f','g' ,'h' ,'\0');
test_pascal( 'e', 'f' );
test_fastcall( 'g', 'h', 'i', 'j' );
test_naked( 'k', 'l');
return 0;
} 匯編代碼如下
int main(int argc, char* argv[])
{
00411350 push ebp
00411351 mov ebp,esp
00411353 sub esp,0C0h
00411359 push ebx
0041135A push esi
0041135B push edi
0041135C lea edi,[ebp-0C0h]
00411362 mov ecx,30h
00411367 mov eax,0CCCCCCCCh
0041136C rep stos dword ptr es:[edi]
test_stdcall( 'a', 'b' );
0041136E push 62h
00411370 push 61h
00411372 call _test_stdcall@8
test_cdecl( 'c','d','e','f','g' ,'h' ,'\0');
00411377 push 0
00411379 push 68h
0041137B push 67h
0041137D push 66h
0041137F push 65h
00411381 push 64h
00411383 push 63h
00411385 call _test_cdecl
0041138A add esp,1Ch ;恢復_test_cdecl參數壓入前的堆棧指令是: add esp,n*4 n=參數的數量
test_fastcall( 'g', 'h', 'i', 'j' );
0041138D push 6Ah
0041138F push 69h
00411391 mov dl,68h
00411393 mov cl,67h
00411395 call test_fastcall
test_naked( 'k', 'l');
0041139A push 6Ch
0041139C push 6Bh
0041139E call _test_naked
return 0;
004113A3 xor eax,eax
}
int __stdcall test_stdcall(char para1, char para2)
{
004111F0 push ebp
004111F1 mov ebp,esp
004111F3 sub esp,0C0h
004111F9 push ebx
004111FA push esi
004111FB push edi
004111FC lea edi,[ebp-0C0h]
00411202 mov ecx,30h
00411207 mov eax,0CCCCCCCCh
0041120C rep stos dword ptr es:[edi] ;初始edi
para1 = para2;
0041120E mov al,byte ptr [para2] ;mov al,byte ptr[ebp+c]
00411211 mov byte ptr [para1],al ;mov byte ptr[ebp+8],al
return 0;
00411214 xor eax,eax
00411216 pop edi
00411217 pop esi
00411218 pop ebx
00411219 mov esp,ebp
0041121B pop ebp
0041121C ret 8 ;恢復到壓入函數參數前堆棧,由于有兩個參數所以ret 8 相當于 pop eip 然后esp+8
}
int __cdecl test_cdecl(char para,... )
{
00411230 push ebp
00411231 mov ebp,esp
00411233 sub esp,0D8h
0041123C lea edi,[ebp-0D8h]
00411242 mov ecx,36h
00411247 mov eax,0CCCCCCCCh
0041124C rep stos dword ptr es:[edi]
char p = '\n';
0041124E mov byte ptr [p],0Ah
va_list marker;
va_start( marker, para );
00411252 lea eax,[ebp+0Ch]
00411255 mov dword ptr [marker],eax
while( p != '\0' )
00411258 movsx eax,byte ptr [p]
0041125C test eax,eax
0041125E je test_cdecl+60h (411290h)
{
p = va_arg( marker, char);
00411260 mov eax,dword ptr [marker]
00411263 add eax,4
00411266 mov dword ptr [marker],eax
00411269 mov ecx,dword ptr [marker]
0041126C mov dl,byte ptr [ecx-4]
0041126F mov byte ptr [p],dl
printf("%c\n", p);
00411272 movsx eax,byte ptr [p]
00411276 mov esi,esp
00411278 push eax
00411279 push offset string "%c\n" (41401Ch)
0041127E call dword ptr [__imp__printf (416180h)]
00411284 add esp,8
0041128E jmp test_cdecl+28h (411258h)
}
va_end( marker );
00411290 mov dword ptr [marker],0
return 0;
00411297 xor eax,eax
004112A9 mov esp,ebp
004112AB pop ebp
004112AC ret
}
int __fastcall test_fastcall(char para1, char para2, char para3, char para4)
{
004112D0 push ebp
004112D1 mov ebp,esp
004112D3 sub esp,0D8h
004112DD lea edi,[ebp-0D8h]
004112E3 mov ecx,36h
004112E8 mov eax,0CCCCCCCCh
004112ED rep stos dword ptr es:[edi]
004112EF pop ecx
004112F0 mov byte ptr [ebp-14h],dl
004112F3 mov byte ptr [ebp-8],cl
para1 = (char)1;
004112F6 mov byte ptr [para1],1
para2 = (char)2;
004112FA mov byte ptr [para2],2
para3 = (char)3;
004112FE mov byte ptr [para3],3
para4 = (char)4;
00411302 mov byte ptr [para4],4
return 0;
00411306 xor eax,eax
0041130B mov esp,ebp
0041130D pop ebp
0041130E ret 8 ;由于使用了ecx ,edx 傳遞參數 本來4個參數只使用兩push 所以這里是 ret 4*2
}
__declspec(naked) void __stdcall test_naked(char para1, char para2)
{
00411330 push ebp ;這里編譯器沒加入任何初始化和清棧的指令,你代碼如何寫它就復制過來
00411331 mov ebp,esp
00411333 push eax
00411334 mov al,byte ptr [para2]
00411337 xchg al,byte ptr [para1]
0041133A pop eax
0041133B pop ebp
0041133C ret 8
}