1. 程式人生 > >VC++函式引數壓棧順序

VC++函式引數壓棧順序

今天閒來無事寫了一段程式碼:
int Func1(int x,int y){
	return (x&y)+((x^y)>>1);
}
void main(){
        int Array[] = {1,2,3,4,5};
	int *ptr = Array;
	int c = Func1(*ptr,*(++ptr));
	printf("%d\n",c);
}

原本預想輸出結果應該為1,誰知竟然為2,百思不得姐之後,就除錯了下進入反彙編檢視其中奧妙:

55:       int Array[] = {1,2,3,4,5};
00401088   mov         dword ptr [ebp-14h],1
0040108F   mov         dword ptr [ebp-10h],2
00401096   mov         dword ptr [ebp-0Ch],3
0040109D   mov         dword ptr [ebp-8],4
004010A4   mov         dword ptr [ebp-4],5
這個過程就不用解釋了,預先計算陣列大小,陣列按記憶體地址從低位到高位分配,接下來:
56:       int *ptr = Array;
004010AB   lea         eax,[ebp-14h]
004010AE   mov         dword ptr [ebp-18h],eax
注意mov和lea的區別,如果你認為lea eax,[ebp-18h]是將記憶體地址為ebp-14h中的內容賦給eax那就錯了,lea只是將記憶體地址賦給eax,也就是此時eax的內容是ebp-14h。對於指令mov eax,[ebp-14h]自然是將記憶體中的地址壓入eax。說到這裡就囉嗦一下,mov的右值必須為常量不能為表示式,如,可以寫MOV EAX, EBP,但不能寫MOV EAX, EBP + 8
這是因為EBP + 8本身也需要一條指令來計算,所以不能跟MOV寫在一條指令裡。但是彙編指令中記憶體地址符可以做算術運算,所以對於指令MOV EAX, [EBP + 8]完全是合法的。接下來繼續跟下一條指令:
57:       int c = Func1(*ptr,*(++ptr));
004010B1   mov         ecx,dword ptr [ebp-18h] //ecx賦值為ebp-14h
004010B4   add         ecx,4  //ecx值為ebp-10h,指向Array[1]
004010B7   mov         dword ptr [ebp-18h],ecx  
004010BA   mov         edx,dword ptr [ebp-18h]  
004010BD   mov         eax,dword ptr [edx]   //Array[1]壓入eax
004010BF   push        eax   //2
004010C0   mov         ecx,dword ptr [ebp-18h]
004010C3   mov         edx,dword ptr [ecx] //ecx值為ebp-10h,指向Array[1]
004010C5   push        edx  //2
004010C6   call        @ILT+0(Func1) (00401005)
004010CB   add         esp,8  
004010CE   mov         dword ptr [ebp-1Ch],eax
根據以上指令我們可以判斷該函式引數的入棧順序為從右至左,所以先執行了++ptr指令,我們繼續往下跟:
58:       printf("%d\n",c);
004010D1   mov         eax,dword ptr [ebp-1Ch]
004010D4   push        eax
004010D5   push        offset string "%d\n" (0043101c)
004010DA   call        printf (004081f0)
004010DF   add         esp,8
很明顯,ebp-1Ch為區域性變數c的地址,在printf中居然也是先將c壓棧,然後才是那一串格式控制字串,這個平時居然沒有注意,那麼對於以下程式碼該輸出多少呢?int i=0;printf("%d,%d,%d",++i,i,--i);輸出該是0,-1,-1,而不是想當然的1,1,0。這個問題可以繼續深入的探討下去,通過查閱資料,得到如下結論:

1. 引數入棧順序是和具體 編譯器實現相關的。比如,Pascal語言中引數就是從左到右入棧的,有些語言中還可以通過修飾符進行指定,如Visual C++。

2. Pascal語言不支援可變長引數,而C語言支援這種特色,正是這個原 因使得C語言函式引數入棧順序為從右至左。具體原因為:C方式引數入棧順序(從右至左)的好處就是可以動態變化引數個數。

3. 這裡面還牽涉到一個新的問題:標準呼叫_stdcall和C呼叫_cdecl,這兩種呼叫方式可等到以後用到時在仔細區別。

最後,對於Func1函式的功能做個說明,這是個面試據說會考到的問題,它的作用是求兩輸入引數的平均值。

完-----------------------------^o^