不定長引數函式的實現
原始碼如下:
int sum(int num, ...)
{
int *p = &num + 1;
int ret = 0;
while(num--)
{
printf("%d\n", num);
ret += *p++;
}
return ret;
}
int main(int argc, char* argv[])
{
printf("%d\n", sum(3, 5, 7, 9));
return 0;
}
所謂不定長引數,就是函式的形引數量不定,型別也可能是不定的。我們把像上面的函式sum中如“int num”這樣的引數叫做“有名引數”,後面用“…”代表的都是“匿名引數”,有名引數是可以在函式中通過變數名直接訪問的,匿名函式則無法通過變數名直接訪問,只能是通過相對有名引數的位置(地址)來訪問了。
關鍵在於:
(1)匿名引數的個數和型別必須通過有名引數傳遞給被調函式
如printf中的第一個引數“const char *fomat”,在format中不僅告訴了printf引數的個數,還必須指定正確的型別,二者缺一不可。
(2)被調函式本身有辦法直接或間接定位引數的個數和型別
即描述引數個數和型別的引數的位置應該是固定的,函式有辦法定位它們,而不是如匿名引數那般不確定的。像C語言的標準呼叫方式,即從右至左壓棧且呼叫方清理棧的方式是比較合適的,尤其是前者,如果在第一個引數中指定各引數的個數和格式,則根據棧的規律可以知道,返回地址上方即是第一個引數(即ebp+8),以後的匿名引數則可依次確定了。
上面原始碼中的sum彙編程式碼如下:
7: int *p = &num + 1;
00401038 lea eax,[ebp+0Ch]
0040103B mov dword ptr [ebp-4],eax
8: int ret = 0;
0040103E mov dword ptr [ebp-8],0
9: while(num--)
00401045 mov ecx,dword ptr [ebp+8]
00401048 mov edx,dword ptr [ebp+8]
0040104B sub edx,1
0040104E mov dword ptr [ebp+8],edx
00401051 test ecx,ecx
00401053 je sum+5Ch (0040107c)
10: {
11: printf("%d\n", num);
00401055 mov eax,dword ptr [ebp+8]
00401058 push eax
00401059 push offset string "%d\n" (0042201c)
0040105E call printf (00401110)
00401063 add esp,8
12: ret += *p++;
00401066 mov ecx,dword ptr [ebp-4]
00401069 mov edx,dword ptr [ebp-8]
0040106C add edx,dword ptr [ecx]
0040106E mov dword ptr [ebp-8],edx
00401071 mov eax,dword ptr [ebp-4]
00401074 add eax,4
00401077 mov dword ptr [ebp-4],eax
13: }
0040107A jmp sum+25h (00401045)
14: return ret;
0040107C mov eax,dword ptr [ebp-8]
15: }
由此又可以進一步驗證兩個問題:
(1)指標的運算。
指標的加減都是以型別的大小為一個單位的,無論是p = &num+1(相當於加4),還是*p++(先計算*p,在執行p++,相當於加4)
如此,若我真的要將地址加1而非加一個數據單位,又當如何呢?
答案是隻能先將&num轉換成單位元組變數指標,如(char *)&num
這種做法其實還是遵循著指標的加減1是資料單位的加減1,只不過是上述做法,將資料單位強制轉換成了1Byte而已。
(2)while(num--)的執行
這個過程看上去有點兒古怪,先是把num的值儲存到了一個臨時變數(暫存器ecx)中,然後執行num--,但是判斷與0的關係的時候,用的又不是num的值,而是臨時變數ecx中儲存的num原值。這是我始料不及的。
這也就是為什麼明明num已經為0了,while還可以執行一次的原因,因為用來做判斷的那個值始終比num的實際值要大一號:P
所有的都記錄下來備查吧:P