这次先用一个简单的求平均数的函数来说明可变参数列表,先放代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int my_average(int n, ...)
{
va_list arg;
va_start(arg, n);
int ret = 0;
int i;
for(i = 0; i < n; i++)
{
ret += (va_arg(arg, int));
}
va_end(arg);
return ret / n;
}

int main()
{
int result = my_average(5, 1,2,3,4,5)
printf("%d\\n", result);
return 0;
}

我们可以看到这个my_average(int n, …)函数的参数部分, n代表后面…可变参数的个数, 因为可变参数的限制,我们需要直接或间接的将参数的个数传递给函数。 函数中有以下几条语句来完成可变参数列表的使用:

1
2
va_list arg;         va_start(arg, n);    
va_arg(arg, int);    va_end(arg);

接下来我们来逐个分析这些语句:

  1. va_list arg;

    通过在IDE中转到定义,我们发现其定义为:
    typedef char* va_list;
    也就是说,这里就是简单的创建了一个类型为char*的变量arg。

  1. 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);的作用就是获取可变参数列表中的第一个参数的地址。

  1. 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’的字符来判断参数的长度?
    这样我们在使用的时候便不需要将参数的个数传递过去,岂不是更方便。
    当然,才疏学浅,可能考虑不周,若哪里不对,还望指出。

  1. va_end(arg);

    va_end定义:
    #define __crt_va_end(ap) ((void)(ap = (va_list)0))
    #define va_end __crt_va_end

    即简单的将arg指向空。