int printf(char * fmt,…)
由此可见,printf为不定参数,难点在于如何获取后面参数。根据函数压栈顺序:
假设参数压栈顺序为从右到左,那arg_1为fmt,我们从参数可以得到fmt,那么为了得到其地址,只要&fmt就可以了,即我们可以通过&fmt得到arg_1的位置。得到了arg_1的位置,我们就可以通过&fmt+offset得到arg_2位置,然后依次就可以得到所有的参数了,但是问题是,offset是多少呢?这个就和arg_2类型有关了,加入arg_2是int,那么offset=4,如果arg_2是float,那么offset=8,
那么我们要怎么得到arg_2类型呢,别忘了,我们还有fmt,我们只要遍历fmt字符串,找到%,然后看一下%后面是什么,就知道arg_2类型是什么了。
为了获取参数,我们需要借助三个宏:
va_list
va_start
va_arg
va_end
三者在内核中定义为:
可变参数是由宏实现的,但是由于硬件平台的不同,编译器的不同,宏的定义也不相同,下面是VC6.0中x86平台的定义 :
typedef char * va_list; // TC中定义为void*
#define _INTSIZEOF(n) ((sizeof(n)+sizeof(int)-1)&~(sizeof(int) - 1) ) //为了满足需要内存对齐的系统
#define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) ) //ap指向第一个变参的位置,即将第一个变参的地址赋予ap
#define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) ) /*获取变参的具体内容,t为变参的类型,如有多个参数,则通过移动ap的指针来获得变参的地址,从而获得内容*/
#define va_end(ap) ( ap = (va_list)0 ) //清空va_list,即结束变参的获取
va_list ap ; 定义一个va_list变量ap
va_start(ap,v) ;执行ap = (va_list)&v + _INTSIZEOF(v),ap指向参数v之后的那个参数的地址,即 ap指向第一个可变参数在堆栈的地址。
va_arg(ap,t) , ( (t )((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )取出当前ap指针所指的值,并使ap指向下一个参数。 ap+= sizeof(t类型),让ap指向下一个参数的地址。然后返回ap-sizeof(t类型)的t类型指针,这正是第一个可变参数在堆栈里的地址。然后 用取得这个地址的内容。
va_end(ap) ; 清空va_list ap。
最后附上自己实现的printf代码
void print(char*fmt,...){
int ival;
float fval;
va_list ap;
va_start(ap,fmt);
for(char*p=fmt;*p!='\0';p++){
if(*p!='%')
{
putchar(*p);
continue;
}
switch (*++p){
case 'd':
ival=va_arg(ap,int);
printf("%d",ival);
break;
case 'f':
fval=va_arg(ap,double);
printf("%f",fval);
break;
case 's':
for (char* sval=va_arg(ap,char*);*sval!='\0';sval++) {
putchar(*sval);
}
break;
default:
putchar(*p);
break;
}
}
va_end(ap);
}
另外,我们可以发散开来,
printf(“%d%d”,2) ; 按照上面的分析,会打印两个,第二个为随机值
printf(“hello”,2,3);只会打印hello