`
huozheleisi
  • 浏览: 1242541 次
文章分类
社区版块
存档分类
最新评论

关于:va_list,va_start,va_arg的3篇文章(ZZ)

 
阅读更多

关于:va_list,va_start,va_arg的3篇文章(ZZ)

文章1:C语言中变长参数(va_list,va_start,va_arg)沉思录
一.引言:
C语言中关于变长参数的使用很简单,无非是如下的框架。是否可以不用宏而编写处理变长参数的函数呢?答案是肯定的,本文作了一些处浅探讨,不足之处望各位批评指正。
使用宏的程序框架:
#include <stdio.h>
#include <stdart.h> /* 或者#include <vararg.h> */
int print (char * fmt, ...)
{
va_list args;
...
va_start(args, fmt);
/* do something here */
va_end (args);
/* do something here too */
}
我看了一下有关va_list,va_start, va_end宏的定义,各编译器不大一样,我着重研究了一下gcc的以上三个宏,并在不用三个宏的情况下编写了测试程序。测试过程和大家分享。
gcc中va_list的定义
#define char* va_list /* gcc中va_list等同char* */
gcc中三个宏的定义如下(经过我加工整理后):
#defineva_start(AP, LASTARG) ( /
AP = ((char *)& (LASTARG) + /
__va_rounded_size(LASTARG)))
#define va_arg(AP, TYPE) ( /
AP += __va_rounded_size(TYPE), /
*((TYPE *)(AP - __va_rounded_size(TYPE))))
#define va_end(AP) /* 没有定义,没有操作 */
有的编译器这样定义:
#define va_end(AP) ((void *)0) /* 有定义,是空操作 */
二.不用宏的处理变长参数实践:
本人在分析了上面的宏后,在Linux、 i386、 gcc、 gas平台的Think Pad R50e上不用上面的三个宏成功写了个测试C代码,并详细分析了该代码的汇编代码,先给出代码如下:
/*****************************************************
* File Name : mytest.c
* Copyright by : Superware
* Version : 0.01
*****************************************************/
#include <stdio.h> /* 我没包含 stdarg.h 或 vararg.h */
void print (char * fmt, ...) { char * arg; /*变长参数的指针
相当于va_list arg */ int i; /* 接受int参数 */ double d; /* 接受double参数 */ char c; /* 接受char 参数*/ char *s; /* 接受字符串 */
printf ("%s", fmt); /* 打印第一个参数 fmt串 */
arg = (char *)&fmt + 4; /* 相当于va_start(arg, fmt)
这里的 +4实际上是
sizeof(char *) 因为在IA32
中,所以我写了4 没有考虑移植,
为什么?在下面解释,
注意这里加 4表示arg已经指向
第二个参数 */ /* 打印第二个参数 */ i = *(int *)arg;/* 接受第二个参数,
为了直接了当,我硬性规定
print()函数的第二个
参数是整数,请看 */
main()函数中的print()
函数调用 */ printf ("%d", i); /* 打印地二个参数,是整数,
所以用格式"%d" */ arg += sizeof(int);/* 指向下一个参数(第三个参数),
为什么是加sizeof(int),
分析汇编码你就明白了 */
/* 打印第三个参数 */
d = *(double *)arg; /* 以下的解释同地二个参数类似,
就不详细解释了 */ printf ("%f", d); arg += sizeof(double);
/* 打印第四个参数 */
c = *(char *)arg; printf ("%c", c); arg += sizeof (char *);
/* 打印第五个参数 */
c = *(char *)arg; printf ("%c", c); arg += sizeof (char *);
/* 打印第六个参数 */
s = *(char **)arg; printf ("%s", s); arg += sizeof (char *);
arg = (void *)0;/* 使arg指针为 (void)0,
实际上就上使无效,否则arg
依然指向第六个参数,危险。*/
/* 相当于 va_end(arg) */
}
int main (void) { print ("Hello/n", 3, 3.6, '/n', 'a', "World/n");
return 0; }
/* File mytest.c ends here *******************/
代码有点长,其实很简单,只是机械地对main()函数中的print()函数中的6个参数进行处理,依次打印上面的6个参数(注意,我没有用格式化符号,带格式话符号的处理函数我将在下面给出),去掉注释,在Linux AS4.2 gcc-3.4.4中编译,运行结果如下:
/************************ Hello 33.600000 aWorld *************************/
与预想的完全一致。说明print()函数对变长参数的理解是正确的。
文章2:(一)写一个简单的可变参数的C函数
下面我们来探讨如何写一个简单的可变参数的C函数.写可变参数的 C函数要在程序中用到以下这些宏:
voidva_start(va_listarg_ptr,prev_param);
typeva_arg(va_listarg_ptr,type);
voidva_end(va_listarg_ptr);
va在这里是variable-argument(可变参数)的意思. 这些宏定义在stdarg.h中,所以用到可变参数的程序应该包含这个 头文件.下面我们写一个简单的可变参数的函数,改函数至少有一个整数 参数,第二个参数也是整数,是可选的.函数只是打印这两个参数的值. voidsimple_va_fun(inti,...)
{
va_listarg_ptr;
intj=0;
va_start(arg_ptr,i);
j=va_arg(arg_ptr,int);
va_end(arg_ptr);
printf("%d%d/n",i,j);
return;
}
我们可以在我们的头文件中这样声明我们的函数:
externvoidsimple_va_fun(inti,...);
我们在程序中可以这样调用: simple_va_fun(100); simple_va_fun(100,200); 从这个函数的实现可以看到,我们使用可变参数应该有以下步骤:
1)首先在函数里定义一个va_list型的变量,这里是arg_ptr,这个变 量是指向参数的指针.
2)然后用va_start宏初始化变量arg_ptr,这个宏的第二个参数是第 一个可变参数的前一个参数,是一个固定的参数.
3)然后用va_arg返回可变的参数,并赋值给整数j.va_arg的第二个 参数是你要返回的参数的类型,这里是int型.
4)最后用va_end宏结束可变参数的获取.然后你就可以在函数里使 用第二个参数了.如果函数有多个可变参数的,依次调用va_arg获 取各个参数.
如果我们用下面三种方法调用的话,都是合法的,但结果却不一样:
1)simple_va_fun(100); 结果是:100-123456789(会变的值) 2)simple_va_fun(100,200); 结果是:100200 3)simple_va_fun(100,200,300); 结果是:100200 我们看到第一种调用有错误,第二种调用正确,第三种调用尽管结果 正确,但和我们函数最初的设计有冲突.下面一节我们探讨出现这些结果 的原因和可变参数在编译器中是如何处理的. (二)可变参数在编译器中的处理 我们知道va_start,va_arg,va_end是在stdarg.h中被定义成宏的, 由于1)硬件平台的不同
2)编译器的不同,所以定义的宏也有所不同,下 面以VC++中stdarg.h里x86平台的宏定义摘录如下('/'号表示折行): typedefchar*va_list; #define_INTSIZEOF(n)/ ((sizeof(n)+sizeof(int)-1)&~(sizeof(int)-1)) #defineva_start(ap,v)(ap=(va_list)&v+_INTSIZEOF(v)) #defineva_arg(ap,t)/ (*(t*)((ap+=_INTSIZEOF(t))-_INTSIZEOF(t))) #defineva_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()改为: voidsimple_va_fun(inti,...) { va_listarg_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*型,当我们忘记用两个参数来调用该函数时,就会出现 coredump(Unix)或者页面非法的错误(window平台).但也有可能不出 错,但错误却是难以发现,不利于我们写出高质量的程序. 以下提一下va系列宏的兼容性. SystemVUnix把va_start定义为只有一个参数的宏:va_start(va_listarg_ptr); 而ANSIC则定义为:va_start(va_listarg_ptr,prev_param); 如果我们要用systemV的定义,应该用vararg.h头文件中所定义的 宏,ANSIC的宏跟systemV的宏是不兼容的,我们一般都用ANSIC,所以 用ANSIC的定义就够了,也便于程序的移植. 小结: 可变参数的函数原理其实很简单,而va系列是以宏定义来定义的,实 现跟堆栈相关.我们写一个可变函数的C函数时,有利也有弊,所以在不必 要的场合,我们无需用到可变参数.如果在C++里,我们应该利用C++的多 态性来实现可变参数的功能,尽量避免用C语言的方式来实现.
文章3:va_start用法(ZZ)

1:当无法列出传递函数的所有实参的类型和数目时,可用省略号指定参数表 void foo(...); void foo(parm_list,...);

2:函数参数的传递原理 函数参数是以数据结构:栈的形式存取,从右至左入栈.eg: #include <iostream> void fun(int a, ...) { int *temp = &a; temp++; for (int i = 0; i < a; ++i) { cout << *temp << endl; temp++; } }

int main() { int a = 1; int b = 2; int c = 3; int d = 4; fun(4, a, b, c, d); system("pause"); return 0; } Output:: 1 2 3 4

3:获取省略号指定的参数 在函数体中声明一个va_list,然后用va_start函数来获取参数列表中的参数,使用完毕后调用va_end()结束。像这段代码: void TestFun(char* pszDest, int DestLen, const char* pszFormat, ...) { va_list args;va_start(args, pszFormat); _vsnprintf(pszDest, DestLen, pszFormat, args); va_end(args); }

4.va_start使argp指向第一个可选参数。va_arg返回参数列表中的当前参数并使argp指向参数列表中的下一个参数。va_end把argp指针清为NULL。函数体内可以多次遍历这些参数,但是都必须以va_start开始,并以va_end结尾。

  1).演示如何使用参数个数可变的函数,采用ANSI标准形式   #include 〈stdio.h〉   #include 〈string.h〉   #include 〈stdarg.h〉   /*函数原型声明,至少需要一个确定的参数,注意括号内的省略号*/   int demo( char, ... );   void main( void )   {    demo("DEMO", "This", "is", "a", "demo!", "");   }   /*ANSI标准形式的声明方式,括号内的省略号表示可选参数*/   int demo( char msg, ... )   { /*定义保存函数参数的结构*/    va_list argp;    int argno = 0;    char para;

   /*argp指向传入的第一个可选参数,msg是最后一个确定的参数*/   va_start( argp, msg );    while (1) {    para = va_arg( argp, char);    if ( strcmp( para, "") == 0 ) break;    printf("Parameter #%d is: %s/n", argno, para);    argno++;    }    va_end( argp );    /*将argp置为NULL*/    return 0;   }

2)//示例代码1:可变参数函数的使用 #include "stdio.h" #include "stdarg.h" void simple_va_fun(int start, ...) { va_list arg_ptr; int nArgValue =start; int nArgCout=0; //可变参数的数目 va_start(arg_ptr,start); //以固定参数的地址为起点确定变参的内存起始地址。 do { ++nArgCout; printf("the %d th arg: %d/n",nArgCout,nArgValue); //输出各参数的值 nArgValue = va_arg(arg_ptr,int); //得到下一个可变参数的值 } while(nArgValue != -1); return; } int main(int argc, char* argv[]) { simple_va_fun(100,-1); simple_va_fun(100,200,-1); return 0; }

3)//示例代码2:扩展——自己实现简单的可变参数的函数。 下面是一个简单的printf函数的实现,参考了<The C Programming Language>中的例子 #include "stdio.h" #include "stdlib.h" void myprintf(char* fmt, ...) //一个简单的类似于printf的实现,//参数必须都是int 类型 { char* pArg=NULL; //等价于原来的va_list char c; pArg = (char*) &fmt; //注意不要写成p = fmt !!因为这里要对//参数取址,而不是取值 pArg += sizeof(fmt); //等价于原来的va_start do { c =*fmt; if (c != '%') { putchar(c); //照原样输出字符 } else { //按格式字符输出数据 switch(*++fmt) { case'd': printf("%d",*((int*)pArg)); break; case'x': printf("%#x",*((int*)pArg)); break; default: break; } pArg += sizeof(int); //等价于原来的va_arg } ++fmt; }while (*fmt != '/0'); pArg = NULL; //等价于va_end return; } int main(int argc, char* argv[]) { int i = 1234; int j = 5678; myprintf("the first test:i=%d/n",i,j); myprintf("the secend test:i=%d; %x;j=%d;/n",i,0xabcd,j); system("pause"); return 0; }

分享到:
评论

相关推荐

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

    1)首先在函数里定义一个va_list型的变量,这里是arg_ptr,这个变 量是指向参数的指针. 2)然后用va_start宏初始化变量arg_ptr,这个宏的第二个参数是第 一个可变参数的前一个参数,是一个固定的参数. 3)然后用va_arg返回...

    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(可变...

    va_start & va_arg.vsdx

    ① va_start宏中,通过&(A)取得的就是...③ 由于va_start & va_arg的运行过程高度依赖参数入栈的顺序(只有函数参数是从右向左入栈时,根据"sdc"字符串的解析才是正确的),这就体现了对编译器设置入栈规范的重要性。

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

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

    va_arg_c_

    A document about cdecl call mode which is used in any platform.

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

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

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

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

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

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

    C++实现mySQL接口

    va_list arg_ptr; 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) { ...

    c++ std::invalid_argument应用

    exception——–&gt;logic_error———&gt;invalid_argument invalid_argument原型是 代码如下: class invalid_argument:public logic_error { public: explicit invalid_argument (const string& what_arg); }; 它在...

    android顶部滑动导航

    //如果标题是第3个及大于第3个时 获取当前标题左端的x坐标值 否则 为 0 int x1=checkedId &gt; 1 ? ((RadioButton) radioGroup.getChildAt(checkedId)).getLeft() : 0; //获取第2个标题左端的x坐标值 ...

    viewpager相关

    View view3 = layoutInflater.inflate(R.layout.view3, null); views.add(view1); views.add(view2); views.add(view3); titles = new ArrayList(); titles.add("tab1"); titles.add("tab2"); ...

    C可变参数函数实现

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

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

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

    query-builder::magnifying_glass_tilted_left:一个用于Wikidata SPARQL查询的简单查询构建器

    安装# Set up and modify the environment variables according to your preferencescp .env.example .env# ensure the node user uses your user id, so you own created filesdocker-compose build --build-arg ...

    git-ray::laptop::water_pistol:-一个nodejs库来调用用RxJS编写的git命令

    git-ray :laptop: :water_pistol: -一个... arg : String , // a string argument to pass as the main param to git params : String [ ] , // git command options nodeSpawnOptions : Object = { cwd : process

    parse_arg:便于解析传递给 main 的参数的库

    ./your_program -l arg file --long_option=arg arg2 解析后,您将在ac参数中获得值 4,在av参数中获得值:["./your_program", "arg", "file", "arg2"] 选择结构 opts结构体用于描述程序期望的参数。 它有以下字段...

    model.ckpt.meda.json

    "stripped_op_list" { "op" { "name": "Add", "input_arg" { "name": "x", "type_attr": "T" }, "input_arg" { "name": "y", "type_attr": "T" }, "output_arg" { "name": "z", "type_...

Global site tag (gtag.js) - Google Analytics