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

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/

  • 4
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值