首先了解一个新知识,函数的参数列表的地址是顺序排列的,以下代码验证这一知识。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <stdio.h>

void func1(int a, int b, int c)
{
printf("%d\n",a);
printf("%d\n",*(&a+1));
printf("%d\n",*(&a+2));
}

void func2(int a, int b, int c)
{
printf("%d\n",a);
printf("%d\n",*((char*)&a+sizeof(a)));
printf("%d\n",*((char*)&a+sizeof(a)*2));
}

int main()
{
func1(1,4,6);//打印1,4,6
func2(2,5,8);//打印2,5,8
return 0;
}

由此来看可变参 va_list,va_start,va_arg,va_end,就可以更好的理解了。

在 stdarg.h 中,定义了这些宏

1
2
3
4
5
6
7
8
9
typedef char *va_list;

#define __va_argsiz(t) (((sizeof(t)+sizeof(int)-1)/sizeof(int))*sizeof(int))

#define va_start(ap, pN) ((ap)=((va_list)(&pN)+__va_argsiz(pN)))

#define va_end(ap) ((void)(ap=(va+list)0))

#define va_arg(ap, t) (((ap)=(ap)+__va_argsiz(t)),*((t*)(void*)((ap)-__va_argsiz(t))))

转换下来,与示例程序类似,区别是取得变长参数地址不同,因为要进行字节对齐,在32位或64位机器上,sizeof(int) 都是 4:

$$
__va_argsiz(t) <=> (\frac{sizeof(t)+sizeof(int)-1}{sizeof(int)}*sizeof(int))
$$

$$
va_start(ap, pN)<=> (ap=(char*)&pN+\frac{sizeof(pN)+sizeof(int)-1}{sizeof(int)}*sizeof(int))
$$

使用的例子:

1
2
3
4
5
6
7
8
9
10
VOID iot_debug_print(CHAR *fmt, ...)
{
char buff[2048] = {0};
va_list args;
va_start(args, fmt);
vsnprintf(buff, 2048, fmt, args);
iot_uart_write(OPENAT_UART_USB, (UINT8*)buff, strlen(buff));
va_end(args);
}