va_list : C语言中的可变参数

  C语言中有些函数使用可变参数,比如常见的int printf( const char* format, ...),第一个参数format是固定的,其余的参数的个数和类型都不固定。但C又无法用面相对象的函数重载的概念。

  不过好在C语言中定义了va_list, va_start( ), va_arg( ), va_end( ) 这样一组宏来处理可变参数问题。

  这组宏在stdarg.h头文件中定义,但是由于1)硬件平台的不同 2)编译器的不同,所以定义的宏也有所不同,下面以VC++中stdarg.h里x86平台的宏定义为例(linux环境中在stdarg.h里发现它将这组宏定义为gcc的内建函数了。__builtin_)

/////////////////////////////////////////////////////////////////////////////////////////////

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)宏是为了考虑那些内存地址需要对齐的系统,从宏的名字来应该是跟sizeof(int)对齐。一般的sizeof(int)=4,也就是参数在内存中的地址都为4的倍数。比如,如果sizeof(n)在1-4之间,那么_INTSIZEOF(n)=4;如果sizeof(n)在5-8之间,那么_INTSIZEOF(n)=8。

(二)

  为了能从固定参数依次得到每个可变参数,va_start, va_arg充分利用下面两点:
  1. C语言在函数调用时,先将最后一个参数压入栈
  2. X86平台下的内存分配顺序是从高地址内存到低地址内存
  高位地址
  第N个参数
  。。。
  第二个参数 (第一个可变参数)
  第一个参数 (固定参数)
  低位地址
  由此可见,v是第一个固定参数( 可变参数函数的第一个参数应该是固定参数,第二个参数开始才是可变的)在内存中的地址,在调用va_start后,ap指向第一个可变参数。这个宏的作用就是在v的内存地址上增加v所占的内存大小,这样就得到了第一个可变参数的地址。
  接下来,可以这样设想,如果我能确定这个可变参数的类型,那么我就知道了它占用了多少内存,依葫芦画瓢,我就能得到下一个可变参数的地址。

(三)

  让我再来看看va_arg,它先ap指向下一个可变参数,然后减去当前可变参数的大小即得到当前可变参数的内存地址,再做个类型转换,返回它的值。以t是int类为例:

     j =  va_arg(ap,int)  =  *(int*)( ( ap += _INTSIZEOF(int) )-_INTSIZEOF(int) )

首先ap+=sizeof(int),已经ap指向下一个参数的地址了.  然后返回ap-sizeof(int)的int*指针,这正是之前一个参数在堆栈里的地址.然后用*取得这个地址的内容(参数值)赋给j.
 


  要确定每个可变参数的类型,有两种做法:要么都是默认的类型,要么就在固定参数中包含足够的信息让程序可以确定每个可变参数的类型。比如,printf,程序通过分析format字符串就可以确定每个可变参数大类型。

(四)

  最后要说的是va_end宏的意思,x86平台定义为ap=(char*)0;使ap不再指向堆栈,而是跟NULL一样.有些直接定义为((void*)0),这样编译器不会为va_end产生代码



举例:

#include<stdio.h>
#include<stdarg.h>
void va_fun(int start,...)
{
 va_list arg_ptr;
 int nArgValue = start;
 int nArgCount = 1;
 va_start(arg_ptr,start);
 while(nArgValue != -1)
 {
 printf("arg %d is:%d\n",nArgCount,nArgValue);
 nArgValue=va_arg(arg_ptr,int);
 ++nArgCount;
 }
 return;
}
main()
{
 va_fun(5,1,7,-1);
 printf("................\n");
 va_fun(2,4,-1);
 printf("................\n");
 va_fun(-1);
 printf("................\n");
 //va_fun(); 可变参数函数最少要有一个参数。
}

原文地址:https://www.cnblogs.com/funway/p/1978657.html