初学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是怎么实现的呢?