C语言中的可变参数列表

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

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

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

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

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

  1. va_list arg;
    通过在IDE中转到定义,我们发现其定义为:
    typedef char* va_list;
    也就是说,这里就是简单的创建了一个类型为char*的变量arg。
  2. va_start(arg, n);
    我们通过转到定义,查看其定义:
    #define __crt_va_start_a(ap, v) \
    ((void)(ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v)))

    #define __crt_va_start(ap, x) __crt_va_start_a(ap, x)

    #define va_start __crt_va_start

    不难发现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);的作用就是获取可变参数列表中的第一个参数的地址。
  3. 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'的字符来判断参数的长度?
    这样我们在使用的时候便不需要将参数的个数传递过去,岂不是更方便。 当然,才疏学浅,可能考虑不周,哪位大佬若是知道原因,还望指教。
  4. va_end(arg);
    va_end定义如下:
    #define __crt_va_end(ap)  ((void)(ap = (va_list)0))
    #define va_end __crt_va_end
    
    即简单的将arg指向空。

 

posted @ 2018-07-25 23:34  Rohdea  阅读(311)  评论(0)    收藏  举报