初探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_listva_start这两个函数有关。

我们看下va_listva_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, ...)类似

获取可变参数的具体做法是

  1. 声明va_list arg_ptr变量
  2. 使用va_start(arg_ptr, max)将arg_ptr指向变参部分
  3. 使用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是怎么实现的呢?

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注