源码如下:
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