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

C语言:va_start、va_end、va_arg 实现可变长参数

2013-08-07 10:19 489 查看
C语言:va_start、va_end、va_arg 实现可变长参数

1、可变长参数

即参数的个数不确定,个数可变。例如printf函数的定义:

int printf( const char* format, ...); 

2、C语言实现

C语言可变参数通过三个宏(va_start、va_end、va_arg)和一个类型(va_list)实现:

void va_start ( va_list ap, paramN );

参数:

ap: 可变参数列表地址 

paramN: 确定的参数

功能:初始化可变参数列表(把函数在 paramN 之后的参数地址放到 ap 中)。

void va_end ( va_list ap );

功能:关闭初始化列表(将 ap 置空)。

type va_arg ( va_list ap, type );

功能:返回下一个参数的值。

va_list :存储参数的类型信息。

3、用法

(1)首先在函数里定义一具va_list型的变量,这个变量是指向参数的指针;

(2)然后用va_start宏初始化变量刚定义的va_list变量;

(3)然后用va_arg返回可变的参数,va_arg的第二个参数是你要返回的参数的类型(如果函数有多个可变参数的,依次调用va_arg获取各个参数);

(4)最后用va_end宏结束可变参数的获取。

4、注意问题

(1)宏定义在 stdarg.h 中,所以使用时,不要忘了添加头文件。

(2)设定一个参数结束标志(cplusplus 上说,va_arg 并不能确定哪个参数是最后一个参数)。

(3)类型的匹配

(4)可变参数的类型和个数完全由程序代码控制,它并不能智能地识别不同参数的个数和类型;

(5)如果我们不需要一一详解每个参数,只需要将可变列表拷贝至某个缓冲,可用vsprintf函数;

(6)因为编译器对可变参数的函数的原型检查不够严格,对编程查错不利.不利于我们写出高质量的代码;
5、实例

#include <stdio.h>
#include <stdarg.h>

#define END -1

int va_sum (int first_num, ...)
{
// (1) 定义参数列表
va_list ap;
// (2) 初始化参数列表
va_start(ap, first_num);

int result = first_num;
int temp = 0;
// 获取参数值
while ((temp = va_arg(ap, int)) != END)
{
result += temp;
}

// 关闭参数列表
va_end(ap);

return result;
}

int main ()
{
int sum_val = va_sum(1, 2, 3, 4, 5, END);
printf ("%d", sum_val);
return 0;
}
6、源码分析

typedef char *  va_list;

#define _crt_va_start(ap,v)  ( ap = (va_list)&(v) + _INTSIZEOF(v) )
//获取可变参数列表的第一个参数的地址(ap是类型为va_list的指针,v是可变参数最左边的参数)

#define _crt_va_arg(ap,t)    ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
//获取可变参数的当前参数,返回指定类型并将指针指向下一参数(t参数描述了当前参数的类型)

#define _crt_va_end(ap)      ( ap = (va_list)0 )
//清空va_list可变参数列表

#define _INTSIZEOF(n)   ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
//获取类型占用的空间长度,最小占用长度为int的整数倍

(1)定义_INTSIZEOF(n)主要是为了某些需要内存对齐的系统.C语言的函数是从右向左压入堆栈的,图(1)是函数的参数在堆栈中的分布位置.我们看到va_list被定义成char*,有一些平台或操作系统定义为void*.再看va_start的定义,定义为&v+_INTSIZEOF(v),而&v是固定参数在堆栈的地址,所以我们运行va_start(ap, v)以后,ap指向第一个可变参数在堆栈的地址。

高地址|-----------------------------| 

|函数返回地址 | 

|-----------------------------| 

|....... | 

|-----------------------------| 

|第n个参数(第一个可变参数) | 

|-----------------------------|<--va_start后ap指向 

|第n-1个参数(最后一个固定参数)| 

低地址|-----------------------------|<-- &v 

图( 1 ) 

然后,我们用va_arg()取得类型t的可变参数值,以上例为int型为例,我们看一下va_arg取int型的返回值: 

j= ( *(int*)((ap += _INTSIZEOF(int))-_INTSIZEOF(int)) ); 

首先ap+=sizeof(int),已经指向下一个参数的地址了.然后返回ap-sizeof(int)的int*指针,这正是第一个可变参数在堆栈里的地址(图2).然后用*取得这个地址的内容(参数值)赋给j. 

高地址|-----------------------------| 

|函数返回地址 | 

|-----------------------------| 

|....... | 

|-----------------------------|<--va_arg后ap指向 

|第n个参数(第一个可变参数) | 

|-----------------------------|<--va_start后ap指向 

|第n-1个参数(最后一个固定参数)| 

低地址|-----------------------------|<-- &v 

图( 2 ) 

最后要说的是va_end宏的意思,x86平台定义为ap=(char*)0;使ap不再指向堆栈,而是跟NULL一样.有些直接定义为((void*)0),这样编译器不会为va_end产生代码,例如gcc在linux的x86平台就是这样定义的. 

在这里大家要注意一个问题:由于参数的地址用于va_start宏,所以参数不能声明为寄存器变量或作为函数或数组类型. 

整理自:
http://www.cnblogs.com/chinazhangjie/archive/2012/08/18/2645475.html http://yijiuzai.blog.163.com/blog/static/10375672720117910402498/
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息