可变参数探析
在c/c++中函数可以有可变参数,最出名的自然是大名鼎鼎的printf.
int printf(__in_z __format_string const char * _Format, ...); //摘自VC8.0 C library head file
其中的...即是c/c++中的可变参数的语法.
而可变参数是用如下四个宏(Macro)实现的.
va_list
va_start
va_arg
va_end
具体怎么用这四个宏来写带可变参数的函数,请参考任何一本 c 教材.
现摘录C语言开山之作 --- C Language Programming中的一个例子.
#include <stdarg.h>
#include <stdio.h>
void minprintf(char *fmt, ...)
{
va_list ap;
char *p, *sval;
int ival;
double dval;
va_start(ap, fmt);
for(p = fmt; *p; p++)
{
if(*p != '%')
{
putchar(*p);
continue;
}
switch(*++p)
{
case 'd':
ival = va_arg(ap, int);
printf("%d", ival);
break;
case 'f':
dval = va_arg(ap, double);
printf("%f", dval);
break;
case 's':
for(sval = va_arg(ap, char*); *sval; sval++)
{
putchar(*sval);
}
break;
default:
putchar(*p);
break;
}
}
va_end(ap);
}
在VC8.0下添加下面的调用并编译.
int main()
{
minprintf("%s - %d - %f - %d/n", "aaa", 10, 2.34, 3);
return 0;
}
output:
F:/var_len_arg/Debug>var_len_arg.exe
aaa - 10 - 2.340000 - 3
咱们看一下这四个宏的定义.
typedef char * va_list;
va_list 只是 char * 的代名词.
#define va_start _crt_va_start
#define _crt_va_start(ap,v) ( ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v) )
#define _ADDRESSOF(v) ( &reinterpret_cast<const char &>(v) )
#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
代换以下va_start(ap, v) ( ap = ( ( (char *)&reinterpret_cast<const char &>(v) ) + /
( (sizeof(v) + sizeof(int) - 1) & ~(sizeof(int) - 1) ) ) )
v是某个参数,ap = v参数的地址 + v参数的类型的大小(sizeof(v)) ,并对整数地址取整
罗索了这么多,这不就是使ap指向 v 参数的下一个参数的地址吗?
举个例子
va_start(ap, fmt);
fmt是char *, 是指针,即sizeof(char*) = 4
ap = (&fmt + 4 + 3) & 0xFFFFFFFC0;
stack
| |
| ... |
|___________|<--- ap = (&fmt + 4 + 3) & 0xFFFFFFFC0;
| |
| fmt |
|___________|<------- &fmt
| |
| |
#define va_arg _crt_va_arg
#define _crt_va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
代换以下 va_arg ( *(t *)((ap += ( (sizeof(t) + sizeof(int) - 1) & ~(sizeof(int) - 1) ) /
( (sizeof(t) + sizeof(int) - 1) & ~(sizeof(int) - 1) ) )
举个例子
ival = va_arg(ap, int);
ival = (*(int *)(((ap + sizeof(int)) & 0xFFFFFFFC0) - ((sizeof(int) + 3) & 0xFFFFFFFC0));
一句话,把ap所指向的4 bytes区域解释成int type,并累增ap,使它指向下一个参数地址, 即ival = *ap++;
stack
| |
| 2.34 |
|___________| <--- new ap (va_arg(ap, int)以后)
| |
| 10 | 10 是 int type, 正好占 4 个bytes 空间
|___________| <--- ap = (&fmt + 4 + 3) & 0xFFFFFFFC0;
| |
| fmt |
|___________|<------- &fmt
| |
| |
那么为什么总有烦人的_INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )呢?
两个原因:
1. 每种类型所占空间是不一样的,比如
sizeof(int) = 4
sizeof(double) = 8
sizeof(char *) = 4
sizeof(char) = 1
sizeof(short) = 2
等等
(sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1)
------
这就是上面画下划线的部分的作用
2. 每个参数的地址必须对齐在 4 字节边界
(sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1)
-------------------
这就是上面画下划线的部分的作用
#define va_end _crt_va_end
#define _crt_va_end(ap) ( ap = (va_list)0 )
等于空,完全可以不需要,估计只是为了与va_start匹配.(最起码在这里VC8.0 C Library中是这样.)
minprintf("%s - %d - %f - %d/n", "aaa", 10, 2.34, 3);
调用的stack如下:
stack
| |
| 3 |
|___________| <--- new ap (va_arg(ap, double)以后)
| |
| |
| | sizeof(double) = 8
| |
| 2.34 |
|___________| <--- new ap (va_arg(ap, int)以后)
| |
| 10 | 10 是 int type, 正好占 4 个bytes 空间
|___________| ap = (&fmt + 4 + 3) & 0xFFFFFFFC0;
| | va_list ap;
| fmt |
|___________| <------- &fmt
| |
| |
上面的解释应该够明白了吧?
如果还有点糊涂,那没办法,只能看 cpu 怎么执行的了!
摘自:http://topic.csdn.net/t/20060824/18/4973562.html
chenxiaohua: http://blog.csdn.net/chenxiaohua/archive/2008/01/12/2039469.aspx
=======================================================================
有关VA_LIST的用法:
VA_LIST 是在C语言中解决变参问题的一组宏
VA_LIST的用法:
(1)首先在函数里定义一具VA_LIST型的变量,这个变量是指向参数的指针
(2)然后用VA_START宏初始化变量刚定义的VA_LIST变量,这个宏的第二个参数是第一个可变参数的前一个参数,是一个固定的参数。
(3)然后用VA_ARG返回可变的参数,VA_ARG的第二个参数是你要返回的参数的类型。
(4)最后用VA_END宏结束可变参数的获取。然后你就可以在函数里使用第二个参数了。如果函数有多个可变参数的,依次调用VA_ARG获取各个参数。
VA_LIST在编译器中的处理:
(1)在运行VA_START(ap,v)以后,ap指向第一个可变参数在堆栈的地址。
(2)VA_ARG()取得类型t的可变参数值,在这步操作中首先apt = sizeof(t类型),让ap指向下一个参数的地址。然后返回ap-sizeof(t类型)的t类型*指针,这正是第一个可变参数在堆栈里的地址。然后用*取得这个地址的内容。
(3)VA_END(),X86平台定义为ap = ((char*)0),使ap不再指向堆栈,而是跟NULL一样,有些直接定义为((void*)0),这样编译器不会为VA_END产生代码,例如gcc在Linux的X86平台就是这样定义的。
要注意的是:由于参数的地址用于VA_START宏,所以参数不能声明为寄存器变量,或作为函数或数组类型。
使用VA_LIST应该注意的问题:
(1)因为va_start, va_arg, va_end等定义成宏,所以它显得很愚蠢,可变参数的类型和个数完全在该函数中由程序代码控制,它并不能智能地识别不同参数的个数和类型. 也就是说,你想实现智能识别可变参数的话是要通过在自己的程序里作判断来实现的.
(2)另外有一个问题,因为编译器对可变参数的函数的原型检查不够严格,对编程查错不利.不利于我们写出高质量的代码。
小结:可变参数的函数原理其实很简单,而VA系列是以宏定义来定义的,实现跟堆栈相关。我们写一个可变函数的C函数时,有利也有弊,所以在不必要的 场合,我们无需用到可变参数,如果在C++里,我们应该利用C++多态性来实现可变参数的功能,尽量避免用C语言的方式来实现。
==========================================================================
变长参数应用举例:
先得声明一个变长参数的变量va_list list
在使用前要先用va_start(list, last_param)对list进行初始化,last_param为最右边的已知参数,表示list
从last_param的下一个参数开始
va_arg(list, 类型)
最后不要忘了用va_end(list)
eg1:
#include<iostream>
#include<iomanip>
#include<stdarg.h>
using namespace std;
double average(int, ...);
int main()
{
double w = 37.5, x = 22.5, y = 1.7, z = 10.2;
cout << setiosflags(ios::fixed | ios::showpoint)
<< setprecision(1) << "w = " << w << "/nx = " << x
<< "/ny = " << y << "/nz = " << z << endl;
cout << average(2, w, x) << endl;
cout << average(3, w, x, y) << endl;
cout << average(4, w, x, y, z) << endl;
return 0;
}
double average(int i, ...)
{
double total = 0;
va_list ap;
va_start(ap, i);
for(int j = 1; j <= i; j++)
{
total += va_arg(ap, double);
}
va_end( ap );
return total/i;
}
eg2:
#include<iostream.h>
#include <stdlib.h>
#include <stdarg.h>
void error(const char*format...);
void main()
{
int a;
char c='d';
char s[100];
error("Enter a string:"); //输入一个字符串
cin>>s;
error("Enter an integer:"); //输入一整数
cin>>a;
error("%s/n%d/n%c/n",s,a,c); //打印输出
}
void error(const char*format...) //实现像printf函数一样的打印输出功能
{
int i;
int j=0;
va_list ap;
va_start(ap,format);
for(i=0;*(format+i)!=0;)
{
int in;
char* pc;
char d;
if(*(format+i)=='%')
{
switch(*(format+i+1))
{
case'd':in=va_arg(ap,int);cout<<in;i=i+2;break;
case's':pc=va_arg(ap,char*);cout<<pc;i=i+2;break;
case'c':d=va_arg(ap,char);cout<<d;i=i+2;break;
default:cout<<'%';i=i+1;break;
}
}
else
{
cout<<*(format+i);
i++;
}
}
}
================================================================
C++变长参数函数的用法
书上说,当无法列出传递函数的所有实参的类型和数目时,可用省略号指定参数表
(...)
如:void foo(...);
void foo(parm_list,...);
void foo(...)
{
//...
}
调用:foo(a,b,c);
就是不懂,把a,b,c的值传进函数里面后,用什么变量来接收???如果不能接收,(...)岂不是没意义?
还有就是不明白
int printf(const char*...);
printf("hello,&s/n",userName);
这个c的输出函数是怎么用(...)实现的.
首先函数体中声明一个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);
}
===========================================================
va_list的用法
还记得printf函数调用的时候那个“...”吗?就是可以输入任意的参数。现在你用va_list也可以实现类似的函数声明,printf就是这样做的。
va_list args; //声明变量
va_start(args, before); //开始解析。args指向before后面的参数
参数类型 var = va_arg(args, 参数类型); //取下一个参数并返回。args指向下一个参数
va_end(args);
摘自:http://hi.baidu.com/flying5/blog/item/aedbe6108c9be8f9c3ce79bb.html