• 深入剖析变长参数函数的实现【转】


    转自:https://blog.csdn.net/hailongchang/article/details/1609720

    什么是变长参数?
    
    所谓含有变长参数的函数是指该函数可以接受可变数目的形参。例如我们都非常熟悉的
    
    printf,scanf等等。
    
    2:变长参数如何实现?
    
    首先来看下面这样一个例子:
    
    #include<stdio.h>
    #include<stdarg.h>
    #include<string.h>
    
    void demo(char *msg,...)
    {
     va_list argp;
     int arg_number=0;
     char *para = msg;
     va_start(argp,msg);
     while(1)
     {
      if ( strcmp( para, "/0") != 0 ) 
      {
       arg_number++;   
       printf("parameter %d is: %s/n",arg_number,para); 
       
      }
      else
       break; 
      para = va_arg(argp,char *);
     }
     va_end(argp); 
    }
    int main()
    {
     demo("Hello","World","/0");
     system("pause");
     return 0;
    }
    
    实现这样一个函数要在内部使用va_list,va_start,va_arg,va_end,这些都是定义在
    
    stdarg.h中的宏。
    
    va_list是定义了一个保存函数参数的数据结构。
    
    va_start(argp,msg)是将argp指向第一个可变参数,而msg是最后一个确定的参数。
    
    最后一个确定的参数的含义是指它以后的参数都是可变参数,如果有下面的函数声明
    
    void demo(char *msg1,char *msg2,...)
    
    那么这里的最后一个确定参数就是msg2。
    
    va_arg(argp,char *)返回当前参数的值,类型为char *,然后将argp指向下一个变长参
    
    数。从这一步可以看出来我们可以通过va_start和va_arg遍历所有的变长参数。
    
    va_end 将argp的值置为0。
    
    
    下面我们看看上述几个宏在visual c++.net 2003 中的实现方法。首先是va_list的实现
    
    #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
    
    
    可以看到va_list实际上是一个机器类型相关的宏,除了alpha机器以外,其他机器类
    
    型都被定义为一个char类型的指针变量,之所以定义为char *是因为可以用该变量逐
    
    地址也就是逐字节对参数进行遍历。
    
    从上面可以看到,这些宏的实现都是和机器相关的,下面是大家常用的IX86机器下宏的
    
    相关定义。
    
    #elif   defined(_M_IX86)
    
    #define _INTSIZEOF(n)   ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
    
    #define va_start(ap,v)  ( ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v) )
    #define va_arg(ap,t)    ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
    #define va_end(ap)      ( ap = (va_list)0 )
    
    #ifdef  __cplusplus
    #define _ADDRESSOF(v)   ( &reinterpret_cast<const char &>(v) )
    #else
    #define _ADDRESSOF(v)   ( &(v) )
    #endif
    
    首先看_INTSIZEOF(n)
    
    我们知道对于IX86,sizeof(int)一定是4的整数倍,所以~(sizeof(int) - 1) )的值一定是
    
    右面[sizeof(n)-1]/2位为0,整个这个宏也就是保证了右面[sizeof(n)-1]/2位为0,其余位置
    
    为1,所以_INTSIZEOF(n)的值只有可能是2,4816,......等等,实际上是实现了字节对齐。
    
    #define va_start(ap,v)  ( ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v) )
    
    所以va_start(ap,v)的作用就很明了了,_ADDRESSOF(v)定义了v的起始地址,_INTSIZEOF(v)定义了v所
    
    占用的内存,所以ap 就指向v后面的参数的起始地址。
    
    #define va_arg(ap,t)    ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
    
    ap += _INTSIZEOF(t) 使ap指向了后面一个参数的地址
    
    而( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )相当于返回了目前t类型的参数的值。
    
    #define va_end(ap)      ( ap = (va_list)0 )
    
    将变量ap 的值置为0。
    
    通过上述分析,再次印证了我么前面对可变参数实现的解释。
    
    
    因此我们可以总结出变长参数函数的一般实现方法:
    
    1:声明原型,形如void demo(char *msg,...),注意变长参数的原型声明中至少要含有
    
    一个确定参数。
    
    2:用va_list定义保存函数参数的数据结构,可以理解为一个指针变量(稍后会解释)。
    
    3:用va_start将上一步定义的变量指向第一个可变参数。
    
    4:用va_arg遍历所有的可变参数。
    
    5:用va_end将指针变量持有的地址值置为0。 
  • 相关阅读:
    JQuery的js写法
    Reapter 中客户端控件和服务器端控件的选择
    ASP.NET 中随时向页面输入数据
    SQL补充查询
    repeater项模版bottom事件获取该bottom所在行id为lblName的label控件的text
    微软Visual Studio 2010架构设计功能应用
    动态表格
    如何选中jsTree中已checked的Item的信息
    据说看完这21个故事的人,30岁前都成了亿万富翁。你是下一个吗?
    WebSocket编解码器
  • 原文地址:https://www.cnblogs.com/sky-heaven/p/9465411.html
Copyright © 2020-2023  润新知