这次先用一个简单的求平均数的函数来说明可变参数列表,先放代码:
1 | int my_average(int n, ...) |
我们可以看到这个my_average(int n, …)函数的参数部分, n代表后面…可变参数的个数, 因为可变参数的限制,我们需要直接或间接的将参数的个数传递给函数。 函数中有以下几条语句来完成可变参数列表的使用:
1 | va_list arg; va_start(arg, n); |
接下来我们来逐个分析这些语句:
va_list arg;
通过在IDE中转到定义,我们发现其定义为:
typedef char* va_list;
也就是说,这里就是简单的创建了一个类型为char*的变量arg。
va_start(arg, n);
我们通过转到定义,发现:
#define va_start __crt_va_start继续转到__crt_va_start的定义:
#define __crt_va_start(ap, x) __crt_va_start_a(ap, x)继续查看__crt_va_start_a(ap,x)的定义:
#define __crt_va_start_a(ap, v) \
((void)(ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v)))不难发现va_start(arg, n);即相当于如下语句:
((void)(arg = (char*)_ADDRESSOF(n) + _INTSIZEOF(n)));其中_ADDRESSOF(v) 定义为:#define _ADDRESSOF(v) (&(v))
即取v的地址
_INTSIZEOF(v)定义为:
#define _INTSIZEOF(n) \
((sizeof(n) + sizeof(int) -1) & ~(sizeof(int) - 1))
即当n小于4个字节时取4个字节,当n大于4个字节小于8个字节时取8个字节,以此类推。联系参数在栈中的存储顺序不难理解va_start(arg, n);的作用就是获取可变参数列表中的第一个参数的地址。
va_arg(arg, int);
va_arg的定义如下:
#define __crt_va_arg(ap, t) \
((t)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)))
#define va_arg __crt_va_arg其作用便是获取当前的参数,并将指针arg移动至下一个参数。
va_arg(arg, int); 即:
((int)((arg += 4) - 4))这里我一开始没看明白,后来明才知道这句写的多么有技巧。
首先让arg变量 加上4 ,然后取原来arg变量所指向的int值。分开来看就比较清楚了,相当于这两句
(int)arg += 4;
*(int)(arg - 4);
其所要表达的意思就是要获取arg当前所指向的int的值,并且还要让arg向后移动4个字节。
之前从没想过这两个语句可以一句搞定。这里不太理解的是,
为什么要让va_arg每获得一个参数便只能获取下一个参数,
为什么不在这个参数列表的最后添加一个固定的参数类似于’\0’的字符来判断参数的长度?
这样我们在使用的时候便不需要将参数的个数传递过去,岂不是更方便。
当然,才疏学浅,可能考虑不周,若哪里不对,还望指出。
va_end(arg);
va_end定义:
#define __crt_va_end(ap) ((void)(ap = (va_list)0))
#define va_end __crt_va_end即简单的将arg指向空。