设为首页 加入收藏

TOP

剖析变长参数函数的实现
2013-11-20 14:24:38 来源: 作者: 【 】 浏览:172
Tags:剖析 参数函数 实现

    什么是变长参数?

    所谓含有变长参数的函数是指该函数可以接受可变数目的形参。例如我们都非常熟悉的 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,4,8,16,……等等,实际上是实现了字节对齐。
    #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.

】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
分享到: 
上一篇C++指向函数的指针 下一篇C/C++ sizeof 总结

评论

帐  号: 密码: (新用户注册)
验 证 码:
表  情:
内  容:

·C++ Lambda表达式保 (2025-12-26 05:49:45)
·C++ Lambda表达式的 (2025-12-26 05:49:42)
·深入浅出 C++ Lambda (2025-12-26 05:49:40)
·C语言指针从入门到基 (2025-12-26 05:21:36)
·【C语言指针初阶】C (2025-12-26 05:21:33)