C语言里的可变参数

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


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

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

void udev_log(struct udev *udev, int priority, const char *file, int line, const char *fn, const char *format, ...) { va_list args; va_start(args, format); udev->log_fn(udev, priority, file, line, fn, format, args); va_end(args); }



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

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

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

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



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

#include <stdio.h> #include <stdarg.h> void foo(char *fmt, ...) { va_list ap; int d; char c, *s; va_start(ap, fmt); while (*fmt) switch (*fmt++) { case 's': /* string */ s = va_arg(ap, char *); printf("string %s/n", s); break; case 'd': /* int */ d = va_arg(ap, int); printf("int %d/n", d); break; case 'c': /* char */ /* need a cast here since va_arg only takes fully promoted types */ c = (char) va_arg(ap, int); printf("char %c/n", c); break; } va_end(ap); }


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

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

void va_start(va_list ap, last);

type va_arg(va_list ap, type);

void va_end(va_list ap);

void va_copy(va_list dest, va_list src);

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

void va_start(va_list ap, last);

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

type va_arg(va_list ap, type);

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

while (*fmt) switch (*fmt++) { case 's': /* string */ s = va_arg(ap, char *); printf("string %s/n", s); break; case 'd': /* int */ d = va_arg(ap, int); printf("int %d/n", d); break; case 'c': /* char */ /* need a cast here since va_arg only takes fully promoted types */ c = (char) va_arg(ap, int); printf("char %c/n", c); break; }

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

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


C语言里的可变参数_第1张图片

假设我们的可变参数是 int n char c ;即我们的函数

void

foo(char *fmt, …)

是这样的

void

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

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



void va_end(va_list ap);

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

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



void va_copy(va_list dest, va_list src);

一般我们会这样赋值:

va_list aq = ap;

或者

va_list aq;

*aq = *ap;

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

va_list aq;

va_copy(aq, ap);

...

va_end(aq);

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

va_copy()

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

ing against an assignment

va_list aq = ap;

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

va_list aq;

*aq = *ap;

Finally, on systems where arguments are passed in registers, it may be necessary for va_start() to allocate memory, store the arguments there, 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 accommodate this situation, C99 adds a

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

va_list aq;

va_copy(aq, ap);

...

va_end(aq);

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

__va_copy instead, since that was the name used in the draft proposal.


你可能感兴趣的:(C语言里的可变参数)