1. 程式人生 > >不定長引數函式的實現

不定長引數函式的實現

原始碼如下:

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