关于变长参数(Variable Number of Arguments)的试验
今天看一些通用类的时候,偶然发现形如
FunName(type name,...)
这样的,... 参数,就是传说中的变长参数了(Variable Number of Arguments)
MSDN的相关解释是这样的
Calls with a Variable Number of Arguments
A partial parameter list can be terminated by the ellipsis notation, a comma followed by three periods (, ...), to indicate that there may be more arguments passed to the function, but no more information is given about them. Type checking is not performed on such arguments. At least one parameter must precede the ellipsis notation and the ellipsis notation must be the last token in the parameter list. Without the ellipsis notation, the behavior of a function is undefined if it receives parameters in addition to those declared in the parameter list.
To call a function with a variable number of arguments, simply specify any number of arguments in the function call. An example is the printf function from the C run-time library. The function call must include one argument for each type name declared in the parameter list or the list of argument types.
All the arguments specified in the function call are placed on the stack unless the __fastcall calling convention is specified. The number of parameters declared for the function determines how many of the arguments are taken from the stack and assigned to the parameters. You are responsible for retrieving any additional arguments from the stack and for determining how many arguments are present. The STDARGS.H file contains ANSI-style macros for accessing arguments of functions which take a variable number of arguments. Also, the XENIX?- style macros in VARARGS.H are still supported.
This sample declaration is for a function that calls a variable number of arguments:
int average( int first, ...);
Microsoft Specific —>
To maintain compatibility with previous versions of Microsoft C, a Microsoft extension to the ANSI C standard allows a comma without trailing periods (,) at the end of the list of parameters to indicate a variable number of arguments. However, it is recommended that code be changed to incorporate the ellipsis notation.
END Microsoft Specific
主要是讲了使用形式和例子,没讲到用例 ( 其实用搜索查 va_list 就可以看到具体例子了 )
在网上查了一些资料
http://www.chinaitpower.com/A/2003-01-07/46251.html
http://blog.csdn.net/chusky/archive/2006/02/22/606346.aspx
http://blog.vckbase.com/iwaswzq/archive/2005/06/20/6912.aspx
http://windtear.net/archives/2005/12/26/000866.html
http://www.chinaunix.net/jh/23/423149.html
http://www.openitpower.com/wenzhang/91/13907_1.html
自己试着写了熟悉下:
归纳来说,在函数定义里面,使用了va_list这个东东
大概是使用方法都是
定义 va_list xxx;
初始化 va_start( xxx, first_p_name) // first_p_name 为函数的第一参数名
使用 returnDate = va_arg(xxx, TYPE) // TYPE 为类型名,使用一些技巧甚至可以是函数指针,上述引用文献中有提到(偶尚未测试)
// 每次调用 xxx 将自动向后推移,第一次处于第二个参数的位置
一直想做成调用的时候,参数表中不要用任何的附加信息,上面给出的函数中
average 隐含了一个规定 用 -1 结束,不是很合理
averageAdv 需要用第一个参数表征后续参数的个数
希望能做到这个样子
averageXP(); // = 0;
// P.S. 虽然不允许 Fun(...) 的形式,但是偶试过可以这样 Fun(int first = 0, ...) :)
averageXP(0); // = 0;
averageXP(1,2); // = 1.5;
averageXP(1,2,3); // = 2;
...
averageXP(1,2,...,n); // = (1+n)/2.0
// ... 表示省略
一开始希望是参照printf的实现,printf使用了
int vprintf( const char *format, va_list argptr );
int vwprintf( const wchar_t *format, va_list argptr );
我想,相类似的,令format="%s",先把参数表弄到char* buf里面,再慢慢分离出来
int vsprintf( char *buffer, const char *format, va_list argptr );
int vswprintf( wchar_t *buffer, const wchar_t *format, va_list argptr );
如同把一个行分离,结合sscanf和strtok很容易就可以办到
但是后来发现其实是不行的……因为这里其实偷换了概念,printf的vprintf就是使用format来确定参数个数的,使用%s这样的东西,是取不到合适的内存的
有文章说,读完参数,va_arg会自动变成NULL,但是好像不是这样的
尝试使用了sizeof来判断,果然是不行的,因为va_list不是一个数据
之后查了va_list的实现,想从中找到方法,结果……基本是死心了,因为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 )
// 注:真实的实现有很多种,主要是适应多个平台,不过思路都是一样的
从这里可以看出,va_list的处理,都是宏,直接在内存中取出来的,而且这里的内存段是联系的,不会向szStr一样,有个0做结束标志,因此是无法判断那些属于参数,那些是之后其他东西的内存,这样从逻辑上,就无法分离出参数了
所以最后也没能写出averageXP~~~~~~~~~ 难怪MSDN里面的例子也不离开最前两种思路~~~~
23:22 2007-3-21
今天又看了一些资料,之前关于va_list的描述不准确,在 _M_ALPHA 这种系统中,va_list是有点类似链表的存在
[stdarg.h]
#ifndef _VA_LIST_DEFINED
#ifdef _M_ALPHA
typedef struct {
char *a0; /* pointer to first homed integer argument */
int offset; /* byte offset of next parameter */
} va_list;
#else
typedef char * va_list;
#endif
#define _VA_LIST_DEFINED
#endif
[MSDN]
Predefined Macros
_M_ALPHA Defined for DEC ALPHA platforms. It is defined as 1 by the ALPHA compiler, and it is not defined if another compiler is used.
受 对C/C++可变参数表的深层探索(http://www.ieee.org.cn/dispbbs.asp?boardID=61&ID=42930) 一文的启发,终于实现了昨天想要的最终效果
主要是思路是利用函数参数调用时要压栈,检查栈顶位置即可
调用如下
printf("/n/n/n");
printf("%lf/n", averageFinal());
printf("%lf/n", averageFinal(1));
printf("%lf/n", averageFinal(1, 2));
printf("%lf/n", averageFinal(1, 2, 3));
printf("%lf/n", averageFinal(1, 2, 3, 4));
printf("%lf/n", averageFinal(1, 2, 3, 4, 5));
// 输出
0.000000
1.000000
1.500000
2.000000
2.500000
3.000000
飘(Piao_Polar)
10:56 2007-3-22