您的位置:首页 > 编程语言 > C语言/C++

C语言里的可变参数

2009-11-26 14:29 162 查看
原创文章,转载请注明出处,谢谢!

作者:清林,博客名:

空静渡



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
函数的栈结构。



假设我们的可变参数是

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.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: