va_list/va_start/va_arg/va_end深入分析【转】

 

转自:http://www.cnblogs.com/justinzhang/archive/2011/09/29/2195969.html

va_list/va_start/va_arg/va_end这几个宏,都是用于函数的可变参数的。

我们来看看在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 )
 
再看看各个宏的功能是什么?
  • va_list用于声明一个变量,我们知道函数的可变参数列表其实就是一个字符串,所以va_list才被声明为字符型指针,这个类型用于声明一个指向参数列表的字符型指针变量,例如:va_list ap;//ap:arguement pointer
  • va_start(ap,v),它的第一个参数是指向可变参数字符串的变量,第二个参数是可变参数函数的第一个参数,通常用于指定可变参数列表中参数的个数。
  • va_arg(ap,t),它的第一个参数指向可变参数字符串的变量,第二个参数是可变参数的类型。
  • va_end(ap) 用于将存放可变参数字符串的变量清空(赋值为NULL).

我们看一段具有可变参数列表的函数来求数组和的代码:

   1:  /*
   2:  *
   3:  *功能: 宏va_arg()用于给函数传递可变长度的参数列表。 
   4:  *首先,必须调用va_start() 传递有效的参数列表va_list和函数强制的第一个参数。第一个参数代表将要传递的参数的个数。 
   5:  *其次,调用va_arg()传递参数列表va_list 和将被返回的参数的类型。va_arg()的返回值是当前的参数。 
   6:  *再次,对所有的参数重复调用va_arg() 
   7:  *最后,调用va_end()传递va_list对完成后的清除是必须的。 
   8:  *
   9:  *时间:2011年8月17日22:34:04
  10:  *作者:张超
  11:  *Email:uestczhangchao@gmail.com
  12:  *
  13:  */
  14:   
  15:   
  16:  #include "X:\编程练习\C-C++\global.h"
  17:   
  18:  #if va_arg==stdon
  19:  #include <stdio.h>
  20:  #include <stdarg.h>
  21:  #include <stdlib.h>
  22:   
  23:  //第一个参数指定了参数的个数
  24:  int sum(int number,...)
  25:  {
  26:      va_list vaptr;
  27:      int i;
  28:      int sum = 0;
  29:      va_start(vaptr,number);
  30:      for(i=0; i<number;i++)
  31:      {
  32:          sum += va_arg(vaptr,int);
  33:      }
  34:      va_end(vaptr);
  35:      return sum;
  36:  }
  37:   
  38:   
  39:  int main()
  40:  {
  41:      printf("%d\n",sum(4,4,3,2,1));
  42:      system("pause");
  43:      return 0;
  44:  }
  45:   
  46:  #endif

 

  • va_start的功能是要把,ap指针指向可变参数的第一个参数位置处,

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

   先取第一个参数的地址,在sum函数中就是取number的地址并且将其转化为char *的(因为char *的指针进行加减运算后,偏移的字节数才与加的数字相同, 如果为int *p,那么p+1实际上将p移动了4个字节),然后加上4(__INITSIZEOF(number)=(4+3)&~3),这样就将ap指向了可变参数字符串的第一个参数。

 


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

 

  • 以int所占的字节为标准进行对其操作。
  • 如果int占四字节,则以四字节对齐为标准读取数据。

    

  • va_arg是要从ap中取下一个参数。

 

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

 

对于这个宏,哥纠结了很久,最后终于搞清楚了,究其原因就是自己C语言功底不扎实,具体表现在没有搞清楚赋值表达式的值是怎么运作的。
我们看这个宏,首先是ap = ap + __INTSIZEOF(t)。注意到,此时ap已经被改变了,它已经指向了下一个参数,我们令x=ap + __INTSIZEOF(t);
那么括号内就变成了(x – __INTSIZEOF(t)),但是这里没有赋值运算符,所以ap的值没有发生变化,此时ap仍然指向的是当前参数的下一个参数的位置,
也就是说ap指向的位置比当前正在处理的位置超前了一个位置。
其实写成下面的形式就简单明了了:

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

 
分析:为什么要将ap指向当前处理参数的下一个参数了?
经过上面的分析,我们知道va_start(ap,v)已经将ap指向了可变参数列表的第一个参数了,以后我们每一步操作都需要将ap移动到下一个
参数的位置,由于我们每次使用可变参数的顺序是:va_start(ap,v)—>va_arg(ap,t);这样我们在第一次去参数的时候,其实ap已经指向了
第二个参数开始的位置,所以我们用表达式的方式获得一个指向第一个参数的临时指针,这样我们就可以采用这种一致的方式来处理可变参数列表。
(感觉没表达的十分清楚,希望各位朋友纠正~~~~~~)。
下图是我的例子程序中去参数的情况(时间仓促,画得很丑,请原谅):
 
 
 
  • va_end(ap)  将声明的ap指针置为空,因为指针使用后最后设置为空。
 
参考资料:
  1. http://topic.csdn.net/u/20110830/15/a3630fc4-3c5f-4a1e-bbee-949ba7b4cbe0.html
  2. http://topic.csdn.net/u/20070120/12/e8b7363b-6404-4d91-9307-01e5ed996f3d.html
时间: 2024-10-11 11:46:07

va_list/va_start/va_arg/va_end深入分析【转】的相关文章

va_list(),va_start(),va_arg(),va_end() 详细解析_C 语言

(一)写一个简单的可变参数的C函数 下面我们来探讨如何写一个简单的可变参数的C函数.写可变参数的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(可变参数)的意思.这些宏定义在stdarg.h中,所以用到可变参数的程序

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

由于在C语言中没有函数重载,解决不定数目函数参数问题变得比较麻烦,即使采用C++,如果参数个数不能确定,也很难采用函数重载.对这种情况,提出了指针参数来解决问题. 如printf()函数,其原型为:int   printf(   const   char*   format,   ...); 它除了有一个参数format固定以外,后面跟的参数的个数和类型是可变的,例如我们可以有以下不同的调用方法:   printf( "%d ",i);   printf( "%s "

一个fprintf的简单封装实例(vsprintf,va_start(),+va_arg(),+va_end()可变参数列表)

对于不方便进行调试的程序,最好的方法,就是将调试信息写入文件了. 下面是一个简单的实例,仅供参考. 程序代码: #include <string.h> #include <stdio.h> #include <stdarg.h> #ifndef DEBUG #define DEBUG #endif int LOG2F(const char *format,...) { int ret = 0; #ifdef DEBUG FILE* fp = NULL; fp=fopen

基础:va_start和va_end使用详解

本文主要介绍va_start和va_end的使用及原理. 介绍va_start和va_end这两个宏之前,先看一下C中传递函数的参数时的用法和原理: 1.在C中,当我们无法列出传递函数的所有实参的类型和数目时,可以用省略号指定参数表 void foo(...); void foo(parm_list,...); 这种方式和我们以前认识的不大一样,但我们要记住这是C中一种传参的形式,在后面我们就会用到它. 2.函数参数的传递原理 函数参数是以数据结构栈的形式存取,从右至左入栈. 举个例子如下:vo

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标准库参考指南(10)stdarg.h

10. stdarg.h stdarg头文件定义了当函数参数个数位置时用于获取参数的宏. 宏: va_start(); va_arg(); va_end(); 类型: typedef va_list 10.1. 变量和定义 va_list适用于进入带有stdarg宏的函数的参数的类型. 一个带有不确定个数参数的函数,用(,...)在参数列表的末尾来标示. 10.2. va_start 声明 : void va_start(va_list ap, last_arg); 初始化与va_arg和va_

VC不定参数的传递和自定义异常的抛出

今天下午学习了两点,一,在vc自定义函数中传定不定个数的参数,即如format函数中的第二个参数"...",能接收任意个数参数;二,自定义异常的抛出,即throw一个自定义异常. 函数代码列如下: 1.// ComboProp.h: interface for the CComboProp class.2.//3.//////////////////////////////////////////////////////////////////////4.#if !defined(AF

再次理解C语言的变参

实在是令我很郁闷的事啊. 去年用了两天的时间恶补了一下变参,今天看到变参.发现头脑一篇空白,啥都不知道了.   古人有云:温故而知新.今日我就在看一遍,做个笔记了.   在 C 语言中,函数参数的 传递方式有值传和址传 . 值传是把实参的一个专用的.临时的复制值给被调函数中相应的形参 被调用函数使用.修改这个传来的复制值,不会影响实参的值 . 址传则 是把变量 ( 实参 ) 的地址传给被调函数 . 被调函数通过这个地址找到该变量的存放位置,直接对该地址中存放 的变量的内容进行存取操作 . 无论是

C/C++语言中可变参数的用法

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