阅读本篇需要具备栈帧的知识,见栈帧详解
看两个可变参数的具体使用例子
求几个数的平均数
#include <stdio.h>
#include <stdarg.h>
int avg(int n, ...)
{
va_list arg;
int i = 0;
int sum = 0;
va_start(arg, n);
for (i = 0; i < n; i++) {
sum += va_arg(arg, int);
}
va_end(arg);
return sum/n;
}
int main()
{
int a = 2;
int b = 4;
int c = 5;
int d = add(a, b, c);
printf("%d", d);
system("pause");
}
函数参数可变部分由 … 构成,第一个参数指明参数个数。
使用时,用va_start 初始化可变参数列表 arg,用va_arg取可变参数列表的值,使用完调用va_end。
需要注意的是可变参数必须从头到尾依次访问。
可变参数解析实质
typedef char* va_list;
...
#define _ADDRESSOF(v) (&(v))
#define _INTSIZEOF(n) ((sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1))
#define __crt_va_start_a(ap, v) ((void)(ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v)))
#define __crt_va_arg(ap, t) (*(t*)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)))
#define __crt_va_end(ap) ((void)(ap = (va_list)0))
上述源码包含在《stdarg.h》里。
可以看出va_start, va_arg ,va_end 都是宏替换,而va_list其实就是一个char *指针;
va_start(ap, v) 这句把传进来的指针ap 指向可变参数部分的第一个参数。
va_arg(ap, t) 返回这个参数类型的值,并且使ap指向下一个参数。
va_end(ap) 使ap指向空
当函数被调用时,就一定会形成临时变量,可变参数函数的临时变量也一样,当我们能确定可变参数的个数和每个参数的具体类型,那么就一定能用指针通过栈帧结构找到所有的临时变量。
由于可变参数的第一个参数是知道的(最后入栈的),所以从第一个参数可以知道参数个数,并且以起为初始地址,在栈中寻找其他可变参数。
因此
va_start 的作用就是取第一个参数的地址,并且加上第一个参数的类型的字节数,达到指向可变参数部分的目的。
va_arg 利用给定的变量类型,解出当前指向的地址的值,并且使指针指向下一个参数地址。
模拟实现printf 库函数
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
void print(char *format, ...)
{
va_list arg;
int i = 0;
int count = 0; //可变参数个数
char *tmp = NULL; //指向参数字符串的指针
int ch = 0;
for (i = 0; i < strlen(format); i++){
if (format[i] >= 97 && format[i] <= 122) {
count++;
}
}
va_start(arg, format);
for (i = 0; i < count; i++) {
if (format[i] == ' ') {
continue;
}
else if (format[i] == 'c') {
ch = va_arg(arg, char);
printf("%c", ch); //完全可以用putchar等函数代替,这里偷懒
}
else if (format[i] == 's') {
tmp = va_arg(arg, char *);
printf("%s", tmp);
}
else if (format[i] == 'd') {
printf("%d", va_arg(arg, int));
}
}
va_end(arg);
}
int main()
{
print("scccd","xxxxx",'b','c','d',100);
system("pause");
}
要搞清的问题–字符串在栈帧中的存储?
其实字符串或者数组传参是形成了指针变量,将指针变量存放在栈帧中,本例中的常量字符串是实参,型参变成了char型指针。