[原]关于变长参数

    关于变长参数(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

   自己试着写了熟悉下:

#include  < stdio.h >
#include 
< stdarg.h >

//  ============================================================================
//     22:18 2007-3-21
//  ============================================================================
double  averageAdv( int  cnt, ...)
{
 
// ~~~~~~~~
  int  i, tmp;
 
double  sum;
 va_list pVa;
 
// ~~~~~~~~

 va_start(pVa, cnt);
 
for  (sum  =   0 , i  =   0 ; i  <  cnt;  ++ i) {
  tmp 
=  va_arg(pVa,  int );
  sum 
+=  tmp;
 }

 va_end(pVa);

 
return  cnt  ?  sum  /  cnt :  0 ;
}

//  ============================================================================
//     21:54 2007-3-21
//  ============================================================================
double  average( int  first, ...)
{
 
// ~~~~~~~~~
  int  tmp, cnt;
 
double  sum;
 va_list pVa;
 
// ~~~~~~~~~

 va_start(pVa, first);
 
for  (sum  =   0 , tmp  =  first, cnt  =   0 ; tmp  !=   - 1 ; tmp  =  va_arg(pVa,  int ),  ++ cnt) {
  sum 
+=  tmp;
 }

 va_end(pVa);

 
return  cnt  ?  sum  /  cnt :  0 ;
}

//  ============================================================================
//  ==============================================================================
int  main()
{
 printf(
" %lf " , average( - 1 ));
 printf(
" %lf " , average( 1 - 1 ));
 printf(
" %lf " , average( 1 2 - 1 ));
 printf(
" %lf " , average( 1 2 3 - 1 ));
 printf(
" %lf " , average( - 1 - 1 ));

 printf(
" " );

 printf(
" %lf " , averageAdv( 0 ));
 printf(
" %lf " , averageAdv( 1 1 ));
 printf(
" %lf " , averageAdv( 2 1 2 ));
 printf(
" %lf " , averageAdv( 3 1 2 3 ));
 printf(
" %lf " , averageAdv( 0 0 ));
 printf(
" %lf " , averageAdv( 1 2 3 ));

 
return   0 ;
}

 

 


     归纳来说,在函数定义里面,使用了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) 一文的启发,终于实现了昨天想要的最终效果

    主要是思路是利用函数参数调用时要压栈,检查栈顶位置即可


int  pos_before_call, pos;

//

//  Use follows together:
//   #define averageFinal recordStack() + averageXP
//   recordStack
//   averageXP
//  Ruoqian,Chen(Piao_Polar) @ 10:50 2007-3-22

#define  averageFinal recordStack() + averageXP

//  ============================================================================
//  ==============================================================================

int  recordStack( void )
{
 __asm
 {
  mov pos_before_call, ebp
 }

 
return   0 ;
}

//  ============================================================================
//  ==============================================================================
double  averageXP( int  first  =   0 , ...)
{
 __asm
 {
  mov pos, ebp
 }

 
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  int  i, tmp, cnt  =  (pos_before_call  -  pos)  /   sizeof ( int );
 
double  sum;
 va_list pVa;
 
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

 va_start(pVa, first);
 
for  (sum  =  first, i  =   1 ; i  <  cnt;  ++ i) {
  tmp 
=  va_arg(pVa,  int );
  sum 
+=  tmp;
 }

 va_end(pVa);

 
return  cnt  ?  sum  /  cnt :  0 ;
}
//

   


调用如下

 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

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值