`
灵动的水
  • 浏览: 190928 次
  • 性别: Icon_minigender_1
  • 来自: 济南
社区版块
存档分类
最新评论

va_start,va_arg,va_end

阅读更多
va_start,va_arg,va_end是在stdarg.h中被定义成宏的,
由于1)硬件平台的不同 2)编译器的不同,所以定义的宏也有所不同,下
面以VC++中stdarg.h里x86平台的宏定义摘录如下(’\’号表示折行): 

typedef char * va_list; 

#define _INTSIZEOF(n) \
((sizeof(n)+sizeof(int)-1)&~(sizeof(int) - 1) ) 

#define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) ) 

#define va_arg(ap,t) \
( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) ) 

#define va_end(ap) ( ap = (va_list)0 ) 

定义_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宏,所
以参数不能声明为寄存器变量或作为函数或数组类型.
关于va_start, va_arg, va_end的描述就是这些了,我们要注意的
是不同的操作系统和硬件平台的定义有些不同,但原理却是相似的. 

(三)可变参数在编程中要注意的问题 

因为va_start, va_arg, va_end等定义成宏,所以它显得很愚蠢,
可变参数的类型和个数完全在该函数中由程序代码控制,它并不能智能
地识别不同参数的个数和类型.
有人会问:那么printf中不是实现了智能识别参数吗?那是因为函数
printf是从固定参数format字符串来分析出参数的类型,再调用va_arg
的来获取可变参数的.也就是说,你想实现智能识别可变参数的话是要通
过在自己的程序里作判断来实现的.
另外有一个问题,因为编译器对可变参数的函数的原型检查不够严
格,对编程查错不利.如果simple_va_fun()改为:
void simple_va_fun(int i, ...)
{
va_list arg_ptr;
char *s=NULL; 

va_start(arg_ptr, i);
s=va_arg(arg_ptr, char*);
va_end(arg_ptr);
printf("%d %s\n", i, s);
return;
}
可变参数为char*型,当我们忘记用两个参数来调用该函数时,就会出现
core dump(Unix) 或者页面非法的错误(window平台).但也有可能不出
错,但错误却是难以发现,不利于我们写出高质量的程序.
以下提一下va系列宏的兼容性.
System V Unix把va_start定义为只有一个参数的宏:
va_start(va_list arg_ptr);
而ANSI C则定义为:
va_start(va_list arg_ptr, prev_param);
如果我们要用system V的定义,应该用vararg.h头文件中所定义的
宏,ANSI C的宏跟system V的宏是不兼容的,我们一般都用ANSI C,所以
用ANSI C的定义就够了,也便于程序的移植. 

va_list-----申请这么一个资源

va_start---将资源初始化

va_arg----得到资源里的变量值

va_end----释放资源



分享到:
评论

相关推荐

    stdarg.h中三个宏va_start ,va_arg和va_end的应用

    2)然后用va_start宏初始化变量arg_ptr,这个宏的第二个参数是第 一个可变参数的前一个参数,是一个固定的参数. 3)然后用va_arg返回可变的参数,并赋值给整数j. va_arg的第二个 参数是你要返回的参数的类型,这里是int型....

    va_list(),va_start(),va_arg(),va_end() 详细解析

    写可变参数的C函数要在程序中用到以下这些宏: 代码如下:void va_start( va_list arg_ptr, prev_param );type va_arg( va_list arg_ptr, type );void va_end( va_list arg_ptr );va在这里是variable-argument(可变...

    关于C/C++中可变参数的详细介绍(va_list,va_start,va_arg,va_end)

    可变参数的函数原理其实很简单,而va系列是以宏定义来定义的,实现跟堆栈相关.我们写一个可变函数的C函数时,有利也有弊,所以在不必要的场合,我们无需用到可变参数。如果在C++里,我们应该利用C++的多态性来实现可变参数...

    stdarg的用法(可变参数的用法)

    这个头文件声明的一个va_list的类型,和三个宏va_start,va_arg,va_end。我们可以生明一个va_list类型的变量,配合三个宏使用。 va_start(arg, last have name arg); 初始化之后,arg将指向第一个无名参数。 va_...

    C可变参数函数实现

    一直以来习惯了使用printf函数,但是对于可变参数没有深入研究过,觉得可变参数是一个神奇的技术。想研究研究看可变参数的... void va_start( va_list arg_ptr, prev_param ); type va_arg( va_list arg_ptr, typ

    C语言参考资料手册

    va_start的作用是初始化ap,因此va_start要在所有其它的va_开头的宏前面最先使用(除了用va_list定义变量外),后面的va_copy, va_arg, va_end都要使用到ap。在一对va_start和va_end之间不能再次使用va_start宏。其中...

    C++实现mySQL接口

    va_start(arg_ptr, _len); DBQueryDataShrPtr dbq = allocDBQyd(); if (_len == 0) _len = strlen(_str); dbq-&gt;dbQstr.append(_str, _len); dbq-&gt;hasHdr = false; while (1) { char* str = 0; str...

    关于变长形参列表函数的设计与使用问题

    针对目前 C 语言教学中长期忽视变长形参列表函数知识点的问题,本文分析了变长形参头文件 &lt;stdarg.h&gt; 中声明的 va_list 变量类型和 va_start 、 va_arg 和 va_end 这 3 个宏的基本使用方法,在此基础上,以实例的方式...

    printf

    印刷品打印文本和数据类型说明符的函数要求* Authorized functions and macros * write (man 2 write) * malloc (man 3 malloc) * free (man 3 free) * va_start (man 3 va_start) * va_end (man 3 va_end) * va_...

    printf:printf的秘密

    va_start (man 3 va_start) va_end (man 3 va_end) va_copy (man 3 va_copy) va_arg (man 3 va_arg) 如果该任务未指定如何处理边缘情况,请执行与printf相同的操作 显示main.c文件作为示例,并用于测试目的 ...

    printf:C语言中的printf

    写(man 2写)malloc(man 3 malloc)空闲(man 3空闲)va_start(man 3 va_start)va_end(man 3 va_end)va_copy(man 3 va_copy)va_arg(man 3 va_arg) 汇编 该代码必须以这种方式编译: * $ gcc -Wall -...

    printf:在C中创建新的printf函数

    授权的函数和宏写入(man 2 write)malloc(man 3 malloc)free(man 3 free)va_start(man 3 va_start)va_end(man 3 va_end)va_copy(man 3 va_copy)va_arg(man 3 va_arg)编译您的代码将这样编译:$ gcc -...

    printf:执行我们自己的printf函数

    va_start (男人3 va_start) va_end (男人3 va_end) va_copy (男人3 va_copy) va_arg (男人3 va_arg) 强制性任务 写函数,该函数生成带有转换说明符c , s和% 。 处理转换说明符d , i 。 为您的功能...

    printf:该项目是对C标准库函数printf的重新创建

    允许使用C语言标准功能: 写(man 2写) va_start(男人3 va_start) va_end(男人3 va_end) va_arg(男人3 va_arg)汇编我们的代码是这样编译的: $ gcc -Wall -Werror -Wextra -pedantic *.c 我们所有的文件都...

    C++可变参数函数的实现方法示例

    C++编程中实现可变参数函数有多种途径,本文介绍一种最常见的实现途径,即可变参数宏方法:形参生命为省略符,函数实现时用... void va_start(va_list ap, last_arg) type va_arg (va_list ap, type) void va_end(v

    参数数目可变函数求平均数,求积。

    本程序设置一个关联函数,其支持任意数目参数的求平均值和求积。运用语句: va_list ap; va_start(ap,int); va_arg(ap,int); va_end(ap)

    c-c++常用头文件及函数1

    可变参数访问宏可变参数开始宏 va_start可变参数结束宏 va_end可变参数访问宏 访问下一个可变参数宏 va_arg头文件 stdio.h输入输出函数:

Global site tag (gtag.js) - Google Analytics