深入剖析变长参数函数的实现
什么是变长参数?
所谓含有变长参数的函数是指该函数可以接受可变数目的形参。例如我们都非常熟悉的
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。
posted on 2020-06-04 09:13 blogernice 阅读(183) 评论(0) 编辑 收藏 举报