深入剖析变长参数函数的实现

Posted sky-heaven

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了深入剖析变长参数函数的实现相关的知识,希望对你有一定的参考价值。

转自: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。 

 

以上是关于深入剖析变长参数函数的实现的主要内容,如果未能解决你的问题,请参考以下文章

深入剖析:一套在 Go 中传递、返回、暴露错误,便于回查的解决方案

Unix环境高级编程编写变长参数函数

C语言--变长参数

c++11新特性:变长参数模板详解

Lua 变长函数参数为 nil

变长函数