va_list可变参数

可变参数函数实现

va_list,va_start,va_arg,va_end

va可变参数意思,variable-argument。

1. 头文件及实现

linux中定义在gcc头文件中,stdarg.h中。

 41 #ifndef __GNUC_VA_LIST

 42 #define __GNUC_VA_LIST

 43 typedef __builtin_va_list __gnuc_va_list;

 44 #endif

 45

 46 /* Define the standard macros for the user,

 47    if this invocation was from the user program.  */

 48 #ifdef _STDARG_H

 49

 50 #define va_start(v,l)   __builtin_va_start(v,l)

 51 #define va_end(v)   __builtin_va_end(v)

 52 #define va_arg(v,l) __builtin_va_arg(v,l)

加builtin前缀的都是编译器内置函数,在机器上找不到源代码。

GCC provides a large number of built-in functions other than the ones mentioned above. Some of these are for internal use in the processing of exceptions or variable-length argument lists and are not documented here because they may change from time to time; we do not recommend general use of these functions.

The remaining functions are provided for optimization purposes.

GCC includes built-in versions of many of the functions in the standard C library. These functions come in two forms: one whose names start with the __builtin_ prefix, and the other without. Both forms have the same type (including prototype), the same address (when their address is taken), and the same meaning as the C library functions even if you specify the -fno-builtin option see C Dialect Options). Many of these functions are only optimized in certain cases; if they are not optimized in a particular case, a call to the library function is emitted.

在VS2008上的一种实现:

   1:  ///stdarg.h

   2:  #define va_start _crt_va_start

   3:  #define va_arg _crt_va_arg

   4:  #define va_end _crt_va_end

   5:  

   6:  ///vadefs.h

   7:  #define _ADDRESSOF(v)   ( &reinterpret_cast<const char &>(v) )

   8:  typedef char *  va_list;

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

  10:  #define _crt_va_start(ap,v)  ( ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v) )

  11:  #define _crt_va_arg(ap,t)    ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )

  12:  #define _crt_va_end(ap)      ( ap = (va_list)0 )

其实际就是利用函数参数入栈的顺序来计算参数地址,从而获知参数值。

函数参数是以数据结构:栈的形式存取,从右至左入栈。

  首先是参数的内存存放格式:参数存放在内存的堆栈段中,在执行函数的时候,从最后一个开始入栈。因此栈底高地址,栈顶低地址,举个例子如下:
void func(int x, float y, char z);
  那么,调用函数的时候,实参 char z 先进栈,然后是 float y,最后是 int x,因此在内存中变量的存放次序是 x->y->z,因此,从理论上说,我们只要探测到任意一个变量的地址,并且知道其他变量的类型,通过指针移位运算,则总可以顺藤摸瓜找到其他的输入变量。

2. 应用

一种实现可查到的实现:

typedef char* va_list;
void va_start ( va_list ap, prev_param ); /* ANSI version */
type va_arg ( va_list ap, type );
void va_end ( va_list ap ); 

va_list 是一个字符指针,可以理解为指向当前参数的一个指针,取参必须通过这个指针进行。
<Step 1> 在调用参数表之前,定义一个 va_list 类型的变量,(假设va_list 类型变量被定义为ap);
<Step 2> 然后应该对ap 进行初始化,让它指向可变参数表里面的第一个参数,这是通过 va_start 来实现的,第一个参数是 ap 本身,第二个参数是在变参表前面紧挨着的一个变量,即“...”之前的那个参数;
<Step 3> 然后是获取参数,调用va_arg,它的第一个参数是ap,第二个参数是要获取的参数的指定类型,然后返回这个指定类型的值,并且把 ap 的位置指向变参表的下一个变量位置;
<Step 4> 获取所有的参数之后,我们有必要将这个 ap 指针关掉,以免发生危险,方法是调用 va_end,他是输入的参数 ap 置为 NULL,应该养成获取完参数表之后关闭指针的习惯。说白了,就是让我们的程序具有健壮性。通常va_start和va_end是成对出现。

3. 使用总结

1)标准C库的中的三个宏的作用只是用来确定可变参数列表中每个参数的内存地址,编译器是不知道参数的实际数目的。

2)在实际应用的代码中,程序员必须自己考虑确定参数数目的办法,如
⑴在固定参数中设标志-- printf函数就是用这个办法。
⑵在预先设定一个特殊的结束标记,就是说多输入一个可变参数,调用时要将最后一个可变参数的值设置成这个特殊的值,在函数体中根据这个值判断是否达到参数的结尾。本文前面的代码就是采用这个办法.

无论采用哪种办法,程序员都应该在文档中告诉调用者自己的约定。

3)实现可变参数的要点就是想办法取得每个参数的地址,取得地址的办法由以下几个因素决定:
      ①函数栈的生长方向
      ②参数的入栈顺序
      ③CPU的对齐方式
      ④内存地址的表达方式
    结合源代码,我们可以看出va_list的实现是由④决定的,_INTSIZEOF(n)的引入则是由③决定的,他和①②又一起决定了va_start的实现,最后va_end的存在则是良好编程风格的体现,将不再使用的指针设为NULL,这样可以防止以后的误操作。

4)取得地址后,再结合参数的类型,程序员就可以正确的处理参数了。
  可变参数的函数原理其实很简单,而va系列是以宏定义来定义的,实现跟堆栈相关.我们写一个可变函数的C函数时,有利也有弊,所以在不必要的场合,我们无需用到可变参数.如果在C++里,我们应该利用C++的多态性来实现可变参数的功能,尽量避免用C语言的方式来实现.

4. 一个示例

对printf一层封装:

void kprintf(int log_level, const char *format, …)

{

va_list ap;

if(log_level <= current_log_level)

{

    if(log_level == KDUMMY)

         return;

    va_start(ap, format);

    vprintf(format, ap);

    va_end(ap);

}

}

注:vprintf以va_list为参数打印输出到标准输出。

The functions printf() and vprintf() write output to stdout, the standard output stream。

int vprintf(const char *format, va_list ap);

参考:

    1. http://www.cnblogs.com/justinzhang/archive/2011/09/29/2195969.html
    2. http://www.cnblogs.com/hanyonglu/archive/2011/05/07/2039916.html
    3. http://blog.csdn.net/edonlii/article/details/8497704
时间: 2024-10-16 23:36:44

va_list可变参数的相关文章

【转】C++可变参数列表处理宏va_list、va_start、va_end的使用

VA_LIST是在C语言中解决变参问题的一组宏他有这么几个成员: 1)va_list型变量: #ifdef     _M_ALPHA typedef    struct{ char* a0; /*pointertofirsthomedintegerargument*/ int offset; /*byteoffsetofnextparameter*/ }va_list; #else typedef    char* va_list;#endif 2)_INTSIZEOF宏,获取类型占用的空间长度

C++可变参数列表处理宏va_list、va_start、va_end的使用

VA_LIST是在C语言中解决变参问题的一组宏他有这么几个成员: 1)va_list型变量: #ifdef     _M_ALPHA typedef    struct{ char* a0; /*pointertofirsthomedintegerargument*/ int offset; /*byteoffsetofnextparameter*/ }va_list; #else typedef    char* va_list;#endif 2)_INTSIZEOF宏,获取类型占用的空间长度

C语言利用va_list、va_start、va_end、va_arg宏定义可变参数的函数

在定义可变参数的函数之前,先来理解一下函数参数的传递原理: 1.函数参数是以栈这种数据结构来存取的,在函数参数列表中,从右至左依次入栈. 2.参数的内存存放格式:参数的内存地址存放在内存的堆栈段中,在执行函数的时候,从最后一个(最右边)参数开始入栈.因此栈底高地址,栈顶低地址,举个例子说明一下: void test(int a, float b, char c); 那么,在调用test函数的时候,实参char c先进栈,然后是float b,最后才是int a,因此在内存中变量的存放次序是c->

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

可变参数的函数原理其实很简单,而va系列是以宏定义来定义的,实现跟堆栈相关.我们写一个可变函数的C函数时,有利也有弊,所以在不必要的场合,我们无需用到可变参数.如果在C++里,我们应该利用C++的多态性来实现可变参数的功能,尽量避免用C语言的方式来实现. 由于在C语言中没有函数重载,解决不定数目函数参数问题变得比较麻烦,即使采用C++,如果参数个数不能确定,也很难采用函数重载.对这种情况,提出了指针参数来解决问题. 如printf()函数,其原型为:int   printf(   const  

宏与可变参数

对于打印函数printf我们太熟悉不过,但真是这样吗?看看其原型:     int printf( const char *format [, argument]... ); 等等,末尾的…是什么意思?省略号?是想留给读者无限的遐想空间?你大可这样认为,因为这是不固定参数,简称不定参数,换句话说,有多少参数都行,只要你敢想. 我们想看看可变参数如何使用,并且怎样实现可变参数.先看下面的例子: #include<stdio.h> #include<malloc.h> #include

可变参数列表的实现

在学习C语言的过程中,大家是不是和我一样,认为printf是一个神一样的函数?他可以接受不同数目,不同类型的参数,他到底是怎么实现的呢? 让我们去看一下它的源代码: printf源代码: int printf(const char *fmt,...) { int res; va_list arg; va_start(arg,fmt); res = vprintf(fmt,arg); va_end(arg); return res; } 它采用的是可变参数列表,可变参数列表主要有以下两个缺点: 1

【C语言】求多个数中的最大值(可变参数列表)

求多个数中的最大值要求用可变参数列表: 代码如下: <span style="font-size:18px;">#include<stdio.h> #include<stdarg.h> int Max(int n,...) { int i=0; int max=0; va_list arg; va_start(arg,n); for(i=0;i<n;i++) { int val=va_arg(arg,int); if (val>max)

C语言的可变参数

可变参数给编程带来了很大的方便,在享受它带来的方便的同时,很有必要了解一下其实现方式,在了解编程语言的同时,也可以扩展编程的思路. 可变参数需要用到3个宏函数和一个类型,他们都定义在<stdarg.h>中,分别是: va_start(vl) va_arg(vl, type) va_end(vl) 其中vl是va_list类型,type就是对象类型(如int, double或 自定义的struct之类的). va_start函数用来初始化vl va_arg(vl, type)用来取得type类型

编写一个可变参数的C函数——头文件stdarg.h中宏va_start ,va_arg和va_end的应用

我们在C语言编程中会遇到一些参数个数可变的函数,例如printf()这个函数,它的定义是这样的:int printf( const char* format, ...);它除了有一个参数format固定以外,后面跟的参数的个数和类型是可变的,例如我们可以有以下不同的调用方法:printf("%d",i);printf("%s",s);printf("the number is %d ,string is:%s", i, s);究竟如何写可变参数的