初探C语言可变参数
初学C语言,有个疑惑,C语言中怎么实现可变参数的。
目前而言,我见过两种写法,一种是在main函数中,另一种是在函数声明中,采用如下形式int func1(char *fmt, ...args)
对于main函数的方式,我理解
int main(int argc, char *argv[])
{
}
应该是编译器,将命令行输入的参数自动转换了char *argv[]的形式,如果我们以这种方式声明的话,那要求调用方事先将参数转换为array of pointer的形式。
但是我们在使用printf函数的时候,并没有先转换为array of pointer的形式,而是直接调用printf(fmt, arg1, arg2);
我们认识到这回main函数的方式不一样,所以让我们看下printf的函数声明,
#include <libioP.h>
#include <stdarg.h>
#include <stdio.h>
#undef printf
/* Write formatted output to stdout from the format string FORMAT.  */
/* VARARGS1 */
int
__printf (const char *format, ...)
{
  va_list arg;
  int done;
  va_start (arg, format);
  done = __vfprintf_internal (stdout, format, arg, 0);
  va_end (arg);
  return done;
}
#undef _IO_printf
ldbl_strong_alias (__printf, printf);
ldbl_strong_alias (__printf, _IO_printf);
我们发现这个声明是这种形式func1(fmt, ...)
那么这里是怎么提取出我们的输入参数的呢,我们看定义,觉得这应该和va_list,va_start这两个函数有关。
我们看下va_list,va_start这些函数的定义,及使用方式。
参看[IBM的文档](va_arg(), va_copy(), va_end(), va_start() — Access function arguments – IBM Documentation),声明如下
#define _ISOC99_SOURCE
#include <stdarg.h>
var_type va_arg(va_list arg_ptr, var_type);
void va_end(va_list arg_ptr);
void va_start(va_list arg_ptr, variable_name);
void va_copy(va_list dest, va_list src);
IBM中对这里的解释是
 The va_start() macro initializes the arg_ptr pointer for subsequent calls to va_arg() and va_end().
The argument variable_name is the identifier of the rightmost named parameter in the parameter list (preceding
, …). Use the va_start() macro before the va_arg() macro. Corresponding va_start() and va_end() macro calls must be in the same function. If variable_name is declared as a register, with a function or an array type, or with a type that is not compatible with the type that results after application of the default argument promotions, then the behavior is undefined.
根据这里的解释,我们大概可以明白va_start的目的是将va_arg等同于func1(fmt, ...)中...所指代的部分
具体是在看么获取参数的呢?
我们通过文档中的例子理解下
/* CELEBV01                                      
   This example passes a variable number of arguments to a function,            
   stores each argument in an array, and prints each argument.                  
 */                                                                             
#include <stdio.h>                                                              
#include <stdarg.h>                                                             
void vout(int max, ...);                                                        
int main(void)                                                                  
{                                                                               
   vout(3, "Sat", "Sun", "Mon");                                                
   printf("\n");                                                                
   vout(5, "Mon", "Tues", "Wed", "Thurs", "Fri");                               
}                                                                               
void vout(int max, ...)                                                         
{                                                                               
   va_list arg_ptr;                                                             
   int args = 0;                                                                
   char *days[7];                                                               
   va_start(arg_ptr, max);                                                      
   while(args < max)                                                            
   {                                                                            
      days[args] = va_arg(arg_ptr, char *);                                     
      printf("Day:  %s  \n", days[args++]);                                     
      }                                                                         
   va_end(arg_ptr);                                                             
}                                                                               
这里的vout的函数声明void vout(int max, ...)与我们printf(fmt, ...)类似
获取可变参数的具体做法是
- 声明
va_list arg_ptr变量 - 使用
va_start(arg_ptr, max)将arg_ptr指向变参部分 - 使用
va_arg获取当前参数的值 
这里关于va_arg函数的应付还需解释下,同样是文档中说明了va_arg的用法
 The va_arg() macro retrieves a value of the given var_type from the location given by arg_ptr and increases arg_ptr to point to the next argument in the list. The va_arg() macro can retrieve arguments from the list any number of times within the function.
这里va_arg首先获取arg_ptr,将该值强转为var_type的值,然后将该值返回,然后指向arg_ptr下一个值
如果arg_ptr越界,那么arg_ptr的返回值是未定义的
这里,我们看到可变参数,似乎需要提前知道参数的个数,可是printf时,我们并没有传递具体的参数。那printf是怎么实现的呢?