研究printf的实现,首先来看看printf函数的函数体
int printf(const char *fmt, ...)
{
int i;
char buf[256];
va_list arg = (va_list)((char*)(&fmt) + 4);
i = vsprintf(buf, fmt, arg);
write(buf, i);
return i;
}
代码位置:D:/~/funny/kernel/printf.c
在形参列表里有这么一个token:...
这个是可变形参的一种写法。
当传递参数的个数不确定时,就可以用这种方式来表示。
很显然,我们需要一种方法,来让函数体可以知道具体调用时参数的个数。
先来看printf函数的内容:
这句:
va_list arg = (va_list)((char*)(&fmt) + 4);
va_list的定义:
typedef char *va_list
这说明它是一个字符指针。
其中的: (char*)(&fmt) + 4) 表示的是...中的第一个参数。
如果不懂,我再慢慢的解释:
C语言中,参数压栈的方向是从右往左。
也就是说,当调用printf函数的适合,先是最右边的参数入栈。
fmt是一个指针,这个指针指向第一个const参数(const char *fmt)中的第一个元素。
fmt也是个变量,它的位置,是在栈上分配的,它也有地址。
对于一个char *类型的变量,它入栈的是指针,而不是这个char *型变量。
换句话说:
你sizeof(p) (p是一个指针,假设p=&i,i为任何类型的变量都可以)
得到的都是一个固定的值。(我的计算机中都是得到的4)
当然,我还要补充的一点是:栈是从高地址向低地址方向增长的。
ok!
现在我想你该明白了:为什么说(char*)(&fmt) + 4) 表示的是...中的第一个参数的地址。
下面我们来看看下一句:
i = vsprintf(buf, fmt, arg);
让我们来看看vsprintf(buf, fmt, arg)是什么函数。
{
char* p;
char tmp[256];
va_list p_next_arg = args;
for (p=buf;*fmt;fmt++) {
if (*fmt != '%') {
*p++ = *fmt;
continue;
}
fmt++;
switch (*fmt) {
case 'x':
itoa(tmp, *((int*)p_next_arg));
strcpy(p, tmp);
p_next_arg += 4;
p += strlen(tmp);
break;
case 's':
break;
default:
break;
}
}
return (p - buf);
}