(转载)不固定多餐函数的编写

如何编写无固定参数函数
2009-12-06 22:21

va_list arg_ptr:定义一个指向个数可变的参数列表指针;

va_start(arg_ptr, argN):使参数列表指针arg_ptr指向函数参数列表中的第一个
可选参数,说明:argN是位于第一个可选参数之前的固定参数,(或者说,最后一
个固定参数;…之前的一个参数),函数参数列表中参数在内存中的顺序与函数声
明时的顺序是一致的。如果有一va函数的声明是void va_test(char a, char b, c
har c, …),则它的固定参数依次是a,b,c,最后一个固定参数argN为c,因此就是
va_start(arg_ptr, c)。

va_arg(arg_ptr, type):返回参数列表中指针arg_ptr所指的参数,返回类型为ty
pe,并使指针arg_ptr指向参数列表中下一个参数。

va_copy(dest, src):dest,src的类型都是va_list,va_copy()用于复制参数列表
指针,将dest初始化为src。

va_end(arg_ptr):清空参数列表,并置参数指针arg_ptr无效。说明:指针arg_pt
r被置无效后,可以通过调用va_start()、va_copy()恢复arg_ptr。每次调用va_st
art() / va_copy()后,必须得有相应的va_end()与之匹配。参数指针可以在参数列
表中随意地来回移动,但必须在va_start() … va_end()之内。


int sum(int nFirst, ...); //一般加一个识别参数
{
   int count = 0, sumAll = 0, i = first;
   va_list marker;

   va_start( marker, first );     //开始查找
   while( i != -1 )
   {
      sumAll += i;
      count++;
      i = va_arg( marker, int);
   }
   va_end( marker );              //结束
   return sumAll;
}

C语言变参函数解析

C语言变参函数解析

1 函数声明
   首先,要实现类似printf()的变参函数,函数的最后一个参数要用 ... 表示,如
int log(char * arg1, ...)
这样编译器才能知道这个函数是变参函数。这个参数与变参函数的内部实现完全没有关系,只是让编译器在编译调用此类函数的语句时不计较参数多少老老实实地把全部参数压栈而不报错,当然...之前至少要有一个普通的参数,这是由实现手段限制的。
2 函数实现
   C语言通过几个宏实现变参的寻址。下面是linux2.18内核源码里这几个宏的定义,相信符合C89,C99标准的C语言基本都是这样定义的。

   typedef char *va_list;

/*
   Storage alignment properties -- 堆栈按机器字对齐
*/
#define _AUPBND           (sizeof (acpi_native_uint) - 1)
#define _ADNBND           (sizeof (acpi_native_uint) - 1)

/*
   Variable argument list macro definitions -- 变参函数内部实现需要用到的宏
*/
#define _bnd(X, bnd)       (((sizeof (X)) + (bnd)) & (~(bnd)))
#define va_arg(ap, T)    (*(T *)(((ap) += (_bnd (T, _AUPBND))) - (_bnd (T,_ADNBND))))
#define va_end(ap)       (void) 0
#define va_start(ap, A)    (void) ((ap) = (((char *) &(A)) + (_bnd (A,_AUPBND))))

   下面以x86 32位机为例分析这几个宏的用途
   要理解这几个宏需要对C语言如何传递参数有一定了解。与PASCAL相反,与stdcall 相同,C语言传递参数时是用push指令从右到左将参数逐个压栈,因此C语言里通过栈指针来访问参数。虽然X86的push一次可以压2,4或8个字节入栈,C语言在压参数入栈时仍然是机器字的size为最小单位的,也就是说参数的地址都是字对齐的,这就是_bnd(X,bnd)存在的原因。另外补充一点常识,不管是汇编还是C,编译出的X86函数一般在进入函数体后立即执行
   push ebp
   mov ebp, esp
   这两条指令。首先把ebp入栈,然后将当前栈指针赋给ebp,以后访问栈里的参数都使用ebp作为基指针。
  
   一一解释这几个宏的作用。
   _bnd(X,bnd) ,计算类型为X的参数在栈中占据的字节数,当然是字对齐后的字节数了。acpi_native_unit是一个机器字,32位机的定义是:typedef u32 acpi_native_uint;
   显然,_AUPBND ,_ADNBND 的值是 4-1 == 3 == 0x00000003 ,按位取反( ~(bnd))就是0xfffffffc 。
因此,_bnd(X,bnd) 宏在32位机下就是
   ( (sizeof(X) + 3)&0xfffffffc )
很明显,其作用是--倘若sizeof(X)不是4的整数倍,去余加4。
   _bnd(sizeof(char),3) == 4
   _bnd(sizeof(struct size7struct),3) == 8

   va_start(ap,A) ,初始化参数指针ap,将函数参数A右边第一个参数的地址赋给ap。 A必须是一个参数的指针,所以此种类型函数至少要有一个普通的参数啊。像下面的例子函数,就是将第二个参数的指针赋给ap。

   va_arg(ap,T) ,获得ap指向参数的值,并使ap指向下一个参数,T用来指明当前参数类型。
   注意((ap) += (_bnd (T, _AUPBND))) 是被一对括号括起来的,然后才减去(_bnd (T, _ADNBND),
而_AUPBND和_ADNBND是相等的。所以取得的值是ap当前指向的参数值,但是先给ap加了当前参数在字对齐后所占的字节数,使其指向了下一个参数。

va_end(ap), 作用是美观。
  
3 总结
先用一个 ... 参数声明函数是变参函数,接下来在函数内部以va_start(ap,A)宏初始化参数指针,然后就可以用va_arg(ap,类型)从左到右逐个获取参数值了

分析到此处算是一清二白了,下面给一个例子
  

int log(char * fmt,...)
{
va_list ap;
int d;
char c, *p, *s;

va_start(ap, fmt);
while (*fmt)
   switch(*fmt++) {
   case 's':    /* string */
   s = va_arg(ap, char *);
   printf("string %s\n", s);
   break;
   case 'd':    /* int */
   d = va_arg(ap, int);
   printf("int %d\n", d);
   break;
   case 'c':    /* char */
   c = va_arg(ap, char);
   printf("char %c\n", c);
   break;
}
va_end(ap);
}

作者: gf86530430   发布时间: 2010-12-07