C语言里的可变参数

来源:互联网 发布:wp embed.min.js 编辑:程序博客网 时间:2024/05/23 16:30

       原创文章,转载请注明出处,谢谢!       
       作者:清林,博客名:
空静渡


c语言中有一种函数,它可以有可变参数,即是说它的参数的个数是不确定的。一个最典型的函数就是printf函数。其实我们自己也可以定义我们的可变参数的函数。

udev的源代码里,我们也可以看到有可变参数的应用,在libudev.c源文件中有以下代码。




这是一个写log的函数,其中就用到了可变参数。

下面我们就来说一下可变参数的用法。

先看下使用可变参数都有哪几个宏。

c89中定义的有va_start(),va_arg(), va_end()这三个宏,在c99中又增加了一va_copy()的宏。



在上面的代码中,我们已经看到了这几个洪的使用,下面我将用一个官方的例子来说明这几个宏的使用。下面是一个官方的例子代码:



下面我将一个一个的说明。

首先,看一下这几个宏的用法:

voidva_start(va_list ap, last);

typeva_arg(va_list ap, type);

voidva_end(va_list ap);

voidva_copy(va_list dest, va_list src);

我们在使用任何的可变参数时,都必须先声明一个va_list变量,而且是在va_start之前声明,如上面的va_listap。在声明完这个变量后就可以使用这几个宏了。

voidva_start(va_list ap, last);

va_start用来初始化我们刚才声明的ap变量,last就是我们的可变参数的前一个参数,如上面的代码中的 va_start(ap,fmt)fmt就是我们可变参数...前的参数。在调用va_start初始化ap后,我们才可以使用va_argva_end这两个宏。在使用va_startap会指向我们参数栈中的可变参数中的第一个参数(我们的函数的参数是放在栈里的,后面用图详细说明)。

typeva_arg(va_list ap, type);

va_arg的作用是取可变参数里的一个参数。ap是我们va_start中初始化中的aptyep是由我们自己指定的一个类型,在调用va_arg后,会返回可变参数中的一个参数,并会使ap指向下一个参数。我们来看下上面的代码:


由上面的程序我们可以知道,如果fmt的值是s的话,那么它后面的第一个参数是一个字符串指针,我们将会取得这个字符串并赋给s变量并打印出来;如果fmt的值是d的话,那么它后面的第一个参数是一个整型值,我们就把这个值赋给变量d并打印出来等等。

我们可以看到,va_arg的第二个参数的类型就返回值的类型,我们为什么要用这个参数呢?我们看下图,下图foo函数的栈结构。



假设我们的可变参数是intncharc;即我们的函数

void

foo(char*fmt, …)

是这样的

void

foo(char*fmt, int n, char c)

那么当我们使用va_start(apfmt)时,我们的ap就指向intn,当我们调用intn1=va_arg(ap,int)时,我们的n1就等于n,而我们需要int这个参数是因为我们要知道n的大小,这样我们的ap就可以指向下一个产生c了,即在调用va_arg(ap,int)后,ap指向c了,这样下次我们就可以方便的取c的值。如果intn的类型出错,例如我们这样shortsva_arg(ap,short)或者根本没有intn这个参数,即后面已经没有参数了,而我们还调用va_arg来获取参数,那么我们就会得到一个不确定的错误的。



voidva_end(va_list ap);

任何调用了va_start后,都必须要调用va_end来清除apap的内部实现为一个指针)。

需要注意的一点是,va_start的宏的实现里带有{括号,而va_end的实现里带有}括号,所以这两个宏必须成对出现,而且只能出现在同一个函数里。



voidva_copy(va_list dest, va_list src);

一般我们会这样赋值:

va_listaq = ap;

或者

va_listaq;

*aq= *ap;

我们看到我们前面的函数的参数是放在栈里的,但是有些系统的函数的参数是放在寄存器里的,因此c99就定义了这个va_copy

va_listaq;

va_copy(aq,ap);

...

va_end(aq);

下面是详细说明的英文,大家参考看看(你可以在linux系统了的man3 stdarg手册里看到)。

va_copy()

An obvious implementation would have a va_list be a pointer to thestack frame of the variadic function. In such a setup (by far themost common) there seems noth‐

ingagainst an assignment

va_listaq = ap;

Unfortunately,there are also systems that make it an array of pointers (of length1), and there one needs

va_listaq;

*aq= *ap;

Finally,on systems where arguments are passed in registers, it may benecessary for va_start() to allocate memory, store the argumentsthere, and also an indication

of which argument is next, so that va_arg() can step through the list. Now va_end() can free the allocated memory again. To accommodatethis situation, C99 adds a

macrova_copy(), so that the above assignment can be replaced by

va_listaq;

va_copy(aq,ap);

...

va_end(aq);

Eachinvocation of va_copy() must be matched by a corresponding invocationof va_end() in the same function. Some systems that do not supply va_copy() have

__va_copyinstead, since that was the name used in the draft proposal.