調用一個函數時,總是先把參數壓入棧,然後通過call指令轉移到被調用函數,在完成調用後清除堆棧.這裡有兩個問題:(1)哪個參數先入棧(2)由誰來清理堆棧.這兩個方面的問題稱為"呼叫慣例(Calling Conventions)"問題.
這裡只討論_stdcall和_cdecl呼叫慣例,前者是Windows API函數常用的呼叫慣例,後者即是C呼叫慣例.
_stcall:參數按從右向左的順序入棧,由被調用函數清理堆棧.
_cdecl :參數按從右向左的順序入棧,由調用函數清理堆棧.
為了防止函數名衝突,或者重載的需要(c++程式),編譯器通常都會修改使用者定義的函數名.
這樣說也許太抽象了,還是來看看具體的例子吧(通常代碼最能說明問題,也最能把問題說清楚).
來看一段簡單的代碼:
Code
void swap(int *x,int *y)
{
int temp;
temp=*x;
*x=*y;
*y=temp;
}
很簡單,即實現兩個數相交換,學過c語言的人對這段代碼應該很熟悉.
再來加一段測試代碼:
Code
int main(int argc, char* argv[])
{
int a=4,b=5;
swap(&a,&b);
printf("a=%d,b=%d\n",a,b);
return 0;
}
以下的測試基於VC++6.0.
(1)swap函數不加任何修飾(最簡單的情況)
來看看它們相應的彙編代碼:
//main調用swap的彙編代碼
swap(&a,&b);
00401096 8D 45 F8 lea eax,[ebp-8]
00401099 50 push eax
0040109A 8D 4D FC lea ecx,[ebp-4]
0040109D 51 push ecx
0040109E E8 62 FF FF FF call @ILT+0(swap) (00401005)
004010A3 83 C4 08 add esp,8
//swap的彙編代碼
void swap(int *x,int *y)
{
00401020 55 push ebp
00401021 8B EC mov ebp,esp
00401023 83 EC 44 sub esp,44h
00401026 53 push ebx
00401027 56 push esi
00401028 57 push edi
00401029 8D 7D BC lea edi,[ebp-44h]
0040102C B9 11 00 00 00 mov ecx,11h
00401031 B8 CC CC CC CC mov eax,0CCCCCCCCh
00401036 F3 AB rep stos dword ptr [edi]
int temp;
temp=*x;
00401038 8B 45 08 mov eax,dword ptr [ebp+8]
0040103B 8B 08 mov ecx,dword ptr [eax]
0040103D 89 4D FC mov dword ptr [ebp-4],ecx
*x=*y;
00401040 8B 55 08 mov edx,dword ptr [ebp+8]
00401043 8B 45 0C mov eax,dword ptr [ebp+0Ch]
00401046 8B 08 mov ecx,dword ptr [eax]
00401048 89 0A mov dword ptr [edx],ecx
*y=temp;
0040104A 8B 55 0C mov edx,dword ptr [ebp+0Ch]
0040104D 8B 45 FC mov eax,dword ptr [ebp-4]
00401050 89 02 mov dword ptr [edx],eax
}
00401052 5F pop edi
00401053 5E pop esi
00401054 5B pop ebx
00401055 8B E5 mov esp,ebp
00401057 5D pop ebp
00401058 C3 ret
@ILT+0(?swap@@YAXPAH0@Z):
00401005 E9 16 00 00 00 jmp swap (00401020)
swap被編譯器修改為 :?swap@@YAXPAH0@Z,這是c++轉換方式.
(2)_stdcall
swap(&a,&b);
004010E6 8D 45 F8 lea eax,[ebp-8]
004010E9 50 push eax
004010EA 8D 4D FC lea ecx,[ebp-4]
004010ED 51 push ecx
004010EE E8 12 FF FF FF call @ILT+0(swap) (00401005)
void _stdcall swap(int *x,int *y)
{
00401030 55 push ebp
00401031 8B EC mov ebp,esp
00401033 83 EC 44 sub esp,44h
00401036 53 push ebx
00401037 56 push esi
00401038 57 push edi
00401039 8D 7D BC lea edi,[ebp-44h]
0040103C B9 11 00 00 00 mov ecx,11h
00401041 B8 CC CC CC CC mov eax,0CCCCCCCCh
00401046 F3 AB rep stos dword ptr [edi]
int temp;
temp=*x;
00401048 8B 45 08 mov eax,dword ptr [ebp+8]
0040104B 8B 08 mov ecx,dword ptr [eax]
0040104D 89 4D FC mov dword ptr [ebp-4],ecx
*x=*y;
00401050 8B 55 08 mov edx,dword ptr [ebp+8]
00401053 8B 45 0C mov eax,dword ptr [ebp+0Ch]
00401056 8B 08 mov ecx,dword ptr [eax]
00401058 89 0A mov dword ptr [edx],ecx
*y=temp;
0040105A 8B 55 0C mov edx,dword ptr [ebp+0Ch]
0040105D 8B 45 FC mov eax,dword ptr [ebp-4]
00401060 89 02 mov dword ptr [edx],eax
}
00401062 5F pop edi
00401063 5E pop esi
00401064 5B pop ebx
00401065 8B E5 mov esp,ebp
00401067 5D pop ebp
00401068 C2 08 00 ret 8
@ILT+0(?swap@@YGXPAH0@Z):
00401005 E9 16 00 00 00 jmp swap (00401020)
(3)_cdecl
void _cdecl swap(int *x,int *y)
與第一種情況相同,即這是vc++預設的方式.
(4)extern "C" _cdecl
void extern "C" _cdecl swap(int *x,int *y)
swap(&a,&b);
00401096 8D 45 F8 lea eax,[ebp-8]
00401099 50 push eax
0040109A 8D 4D FC lea ecx,[ebp-4]
0040109D 51 push ecx
0040109E E8 67 FF FF FF call @ILT+5(_swap) (0040100a)
004010A3 83 C4 08 add esp,8
@ILT+5(_swap):
0040100A E9 11 00 00 00 jmp swap (00401020)
swap被編譯器修改為_swap,即加一個底線(c語言轉換方式),其餘與第三種情況相同.
(5)extern "C" _stdcall
void extern "C" _stdcall swap(int *x,int *y)
swap(&a,&b);
00401096 8D 45 F8 lea eax,[ebp-8]
00401099 50 push eax
0040109A 8D 4D FC lea ecx,[ebp-4]
0040109D 51 push ecx
0040109E E8 62 FF FF FF call @ILT+0(_swap@8) (00401005)
swap被修改為_swap@8,即加一個底線,後面加上參數在堆棧中所佔的位元組數(c語言轉換方式),其餘與第二種情況相同.