我们都很熟悉,printf()函数里面可以实现自定义参数的个数和类型,那么底层到底是怎么实现的呢?
参考网上一个简易的printf的实现
void minprintf(char *fmt, ...)
4 {
5 va_list ap;
6 char *p, *sval;
7 int ival;
8 double dval;
9
10 va_start(ap, fmt);
11 for (p = fmt; *p; p++) {
12 if(*p != '%') {
13 putchar(*p);
14 continue;
15 }
16 switch(*++p) {
17 case 'd':
18 ival = va_arg(ap, int);
19 printf("%d", ival);
20 break;
21 case 'f':
22 dval = va_arg(ap, double);
23 printf("%f", dval);
24 break;
25 case 's':
26 for (sval = va_arg(ap, char *); *sval; sval++)
27 putchar(*sval);
28 break;
29 default:
30 putchar(*p);
31 break;
32 }
33 }
34 va_end(ap);
35 }
在stdarg.h里面能发现这些:
typedef char * va_list;
#define _INTSIZEOF(n) \
((sizeof(n)+sizeof(int)-1)&~(sizeof(int) - 1) )
#define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) )
#define va_arg(ap,t) \
( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
#define va_end(ap) ( ap = (va_list)0 )
现在分析 #define _INTSIZEOF(n) \ ((sizeof(n)+sizeof(int)-1)&~(sizeof(int) - 1) ) ,
假设这里的n是char.
sizeof(char)是1;
sizeof(int)是4;
==>((sizeof(n)+sizeof(int)-1)是4,化为二进制是00000100B
假设cpu为32位.(sizeof(int)-1)是3,化为二进制是00000011B,取反得到11111100B,
相与得到00000100B,即是4.
假设这里的n是short,按照上面的做法得到00000100B,即是4.
假设这里的n是int,按照上面的做法得到00000100B,即是4.
假设这里的n是double,按照上面的做法得到000001000B,即是8.
这个宏的功能就是以4为单位进行向下对齐,比如0 1 2 3都得到0, 4 5 6 7 都得到4,这个功能是
为了数据在内存中进行对齐,方便cpu进行取数据用的.
然后分析#define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) ):
常用的printf形如(“number is %d,%c”,a,’b’), 在上面函数的第10行可以看到 va_start (ap, fmt),
这里的fmt就是”numbei is %d”的首地址,一个4字节指针.ap就指向它. 经过处理已经开始
执行到case ‘d’: ival = va_arg(ap, int);替换后得到
( (int )((ap += _INTSIZEOF(int)) - _INTSIZEOF(int)) ), 这时ap指向了第2个可变参数即’b’.
但是通过- _INTSIZEOF(int)返回的是变量a的地址,转化的(int*)型指针,然后 * ( int * )取值返回给ival.
全部处理完后#define va_end(ap) ( ap = (va_list)0 ),意思就是把原来的ap重新指向0地址,因为在虚拟内存0
地址,不可访问,强行取值会段错误,这样就避免了野指针的产生.