在C语言中,函数的参数是可变长的。虽然在一般的应用程序中不会去定义可变参数的函数,但是我们经常会使用标准库提供的可变参数函数。最典型的就是printf函数,它的声明如下:

<stdio.h>
int printf(const char *fmt, ...);

       那如何在C语言定义并使用可变参数函数呢?在C标准库stdarg.h中,为我们定义了一系列的宏,借助其中的va_list、va_arg、va_start、va_end宏就可以实现可变参数的函数。

<stdarg.h>

typedef char *va_list;
#define  _AUPBND                1
#define  _ADNBND                1
#define _bnd(X, bnd)            (((sizeof (X)) + (bnd)) & (~(bnd)))
#define va_arg(ap, T)           (*(T *)(((ap) += (_bnd (T, _AUPBND))) - (_bnd (T,_ADNBND))))
#define va_end(ap)              (void) 0
#define va_start(ap, A)         (void) ((ap) = (((char *) &(A)) + (_bnd (A,_AUPBND))))

       va_list:用于定义一个指向可变参数空间的起始地址的指针变量,它实际是一个char *类型的指针。va_start:用于初始化一个va_list变量,它接受两个参数,第一个参数为va_list类型的变量,第二个参数是传递给可变参数函数的最右侧的参数(例如,printf中的参数fmt),通过第二个参数的地址可以计算出指向可变参数的起始地址。va_arg:用于依次获取可变参数列表中的各个参数,它也接受两个参数,第一个参数为va_list类型的变量,第二个参数为期望参数的数据类型,用于告知编译器当前获取的参数的数据类型。 va_end:只是用于说明可变参数已经访问完成,并未做任何实际的操作,定义为(void)0。

       通过如下实例来说明上述宏控的具体用法:函数sum_list()用于计算传递给它的所有可变参数之和,其第一个参数用于给定可变参数的个数以及计算可变参数的起始位置,第二个参数相当于占位符,用于表示可变的参数列表。

#include <stdio.h>
#include <stdarg.h>

int sum_list(int n, ...)
{
        int i = 0;
        int sum = 0;
        va_list var;

        va_start(var, n);
        for ( ; i < n; ++i) {
                sum += va_arg(var, int);
        }
        va_end(var);

        return sum;
}

int main()
{
        /*计算1-5之和*/
        printf("sum = %d\n", sum_list(5,1,2,3,4,5));
        return 0;
}

       在定义可变参数以及使用过程中,需要注意在stdarg定义的宏控,是没有办法知道可变参数的长度和参数的类型,这些需要显示的提供。如:printf函数需要通过格式化的字符串(%d、%s、%c等),告知编译器当前的可变参数的长度和参数的类型。若提供的参数类型不正确,那计算的结果是未定义的。另外可变参数函数必须有一个已知的参数A用于计算可变参数的地址。


参考文档:

       1. POINTERS ON C(C和指针) 【美】 Kenneth A. Reek 著

       2. The Standard C Library(C标准库) 【美】 P.J. Plauger 著