printf可变参数原理

了解了下可变参函数的原理,这里记录下方便自己以后看

printf函数原型 int printf(const char *fmt, ...)

这里写个例子方便理解 printf("%s\n","hello");

函数传参参数压栈的方向是从右往左,也就是说当调用printf函数的时候,先是最右边的“hello”参数入栈,然后才是

“%s\n”,指针也是变量,也有地址,入栈入的是这两个指针的地址,栈是从高地址向低地址方向增长的。所以在栈内地址顺序

是 ("hello" 指针的地址 [ 高 ]) ->("%s\n"指针的地址 [ 低 ]),而“%s\n”的地址是知道的,就是 fmt参数的地址,用这个地址加上一个指针的长度就是传入的第一个参数的地址,也就是这里的“hello”的地址,所以 fmt+sizeof(char *)就是第一个参数,由于在同一系统中指针长度是固定的,这样就可以算出第2个,第3个以及后面的参数,再从fmt字符串中找到对应的%后面的字符,就能确定需要打印几个参数。

 

 

 

当调用printf时参数的个数是不限定的,那么该函数是如何实现的呢?来看一下该函数的定义

 int printf(const char *format,[argument]...)

printf的第一个参数就是那个字符指针即为被双引号括起来的那一部分,函数通过判断字符串里控制参数的个数(%d等等)来判断参数个数及数据类型。例如printf("%d,%d",a,b);汇编代码为:

.section

.data

string out = "%d,%d"

push b

push a

push $out

call printf

注意参数的入栈顺序,最后的先压入栈中,最先的后压入栈中,参数控制的那个字符串常量是最后被压入的 

 

该函数的实现涉及到几个宏:

 C中变长实参头文件stdarg.h提供了一个数据类型va_list和三个宏(va_start、va_arg和va_end),用它们在被调用函数不知道参数个数和类型时对可变参数表进行测试,从而为访问可变参数提供了方便且有效的方法。

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 )

 

  • va_list是一个char类型的指针,当被调用函数使用一个可变参数时,它声明一个类型为va_list的变量,该变量用来指向va_arg和va_end所需信息的位置。
  • _INTSIZEOF(n):为了字节对齐,将n的长度化为int长度的整数倍。在32位系统中,~(sizeof(int)–1) )展开为~(4-1)=~(00000011b)=11111100b,这样任何数& ~(sizeof(int)–1) )后最后两位肯定为0,也就是4的整数倍。
  • va_start:获取到可变参数表的首地址,并将该地址赋给指针ap
  •  va_arg获取当前ap所指向的可变参数,并将指针ap指向下一个可变参数。注意,该宏的第二个参数为类型
  • va_end:   结束可变参数的获取

下面是printf的实现

static char sprint_buf[1024];

int printf(char *fmt, ...)

{

va_list args;

int n;

va_start(args, fmt);

n = vsprintf(sprint_buf, fmt, args);

va_end(args);

write(stdout, sprint_buf, n);

return n;

}

 

从上面的代码来看,printf似乎并不复杂,它通过一个宏va_start把所有的可变参数放到了由args指向的一块内存中,然后再调用
vsprintf真正的参数个数以及格式的确定是在vsprintf搞定的。

int vsprintf(char *string, char *format, va_list args)

该函数将args按格式format写入字符串string中,该函数中会用到va_arg来获取各个参数,正常情况下返回生成字串的长度(除去\0),错误情况返回负值

下面是各个宏的一个简单的应用例子,实现一个多参数的printf

 
void minprintf(const char *fmt, ...)

{

va_list ap;

const char *p, *sval;

int ival;

double dval;


va_start(ap, fmt);

for (p = fmt; *p; p++) {

if (*p != '%') {

putchar(*p);

continue;

}

switch (*++p) {

case 'd':

ival = va_arg(ap, int);

printf("%d", ival);

break;

case 'f':

dval = va_arg(ap, double);

printf("%f", dval);

break;

case 's':

for (sval = va_arg(ap, char *); *sval; sval++)

putchar(*sval);

break;

default:

putchar(*p);

break;

}

}

va_end(ap);

}

 

参考 从printf谈可变参数函数的实现 https://www.cnblogs.com/hnrainll/archive/2011/08/05/2128496.html  

--------------------- 本文来自 Alatebloomer 的CSDN 博客 ,全文地址请点击:https://blog.csdn.net/Alatebloomer/article/details/81296383?utm_source=copy

你可能感兴趣的:(printf可变参数原理)