c语言实现可变参数的方式是利用函数参数入栈从右到左的的顺序,如果知道任意参数地址,且知道参数类型,则可以通过指针移动取出参数值。
比如函数void fun(int a,int x,int y,int z)
入栈顺序如下
再看va_list、va_start、va_arg、va_end
,这几个是定义在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 ) // 将指针置为无效
void va_start ( va_list ap, prev_param ); /* ANSI version */
type va_arg ( va_list ap, type );
void va_end ( va_list ap );
_INTSIZEOF(n)
- 是求n变量的sizeof之后再对int字节对齐,比如sizeof(n)=1,则_INTSIZEOF(n)=4,sizeof(n)=3,则_INTSIZEOF(n)=8
va_start(ap,v)
- 是让ap指向v变量的下一个变量,如果对于
void fun(int a,int x,int y,int z)
,va_start(ap,x),则ap指向x的地址
va_arg(ap,t)
- ap指向下一个变量地址,并返回ap加_INTSIZEOF(t)之前地址里面的值(注意ap一直在增加)
va_end(ap)
- 将ap设置为NULL
下面是一个例子
#include <stdio.h>
#include <stdarg.h>
int va_printf_demo(int num,...)
{
va_list p_arg;
va_start(p_arg,num);
for(int i=0;i<num;i++)
{
printf("%s ",va_arg(p_arg,char*));
}
va_end(p_arg);
printf("\n");
}
int main(int argc,char**argv)
{
va_printf_demo(4,"a","b","c","d");
return 0;
}
结果
再来看下printf可以怎么实现
const unsigned char cr = '\r';
int printf(const char* format, ...)
{
int i;
va_list va;
va_start(va, format);
char buffer[256];
int ret = vsnprintf(buffer, 256, format, va);
va_end(va);
for (i = 0; i < ret; ++i) {
if(buffer[i] == '\n')
{
drv_uart_write_fifo(&cr,1,UART_BLOCKING);
}
drv_uart_write_fifo((unsigned char*)&buffer[i],1,UART_BLOCKING);
}
return ret;
}