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^