转:C语言可变参数实现
C语言可变参数实现
第一:什么是可变参数
int printf(const char* format, ...);
看到printf的定义大家就知道了,只有一个固定的const char*参数,后面的都是不定长的参数列表了。
第二:自己写一个可变参数函数
1.参数形参方式,跟printf类似,第一个为固定参数,后面的用...代替;
2.包含stdarg.h头文件,因为需要用到几个里面定义的宏;
void va_start(va_list arg_ptr, prev_param);
type va_arg(va_list arg_ptr, type);
void va_end(va_list arg_ptr);
va是variable argument可变参数的意思。
3.函数里面定义一个va_list类型的变量,它是存储参数地址的指针,因为得到参数的地址之后,再结合参数的类型,才能得到参数的值。
4.用va_start宏初始化3中定义的va_list类型变量,
5.用va_arg宏获得下一个参数的值
6.设定结束条件。
例子程序:
#include<stdarg.h> //required by variable argument list
void simple_va_function(int start,...){
va_list arg_ptr;
int nArgValue = start;
int nArgCount = 0; //count of arguments
va_start(arg_ptr, start); //to obtain beginning address of variable arguments base on the given arguemnt
do{
nArgCount++;
printf("The %d argument is %d.\n", nArgCount, nArgValue);
nArgValue = va_arg(arg_ptr, int);
}while(nArgValue != -1);
va_end(arg_ptr);
return;
}
int main(){
simple_va_function(1, 2, 3, 4, -1); //need to set a flag to specify the end of argument list
return 0;
}
第三:可变参数的编译器实现原理,这几个宏的实现。
首先列出stdarg.h里面的宏定义(注意这个宏定义与硬件平台和编译器有关,这里是VC6的宏定义):
typedef char * 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被定为一般指针,有些机器使用void*,有些使用char*,都是可以的。
_INTSIZEOF(char*) = 4
_INTSIZEOF(2byte) = 4
_INTSIZEOF(double)= 8
_INTSZEOF(n)是考虑了内存对齐后的变量占用空间,自己考虑一下,代进去小于4字节的类型进去算一下,就知道在这个平台上(32位机),内存对齐到4个字节。
(1) va_start就是求得第一个可变参数的地址
#define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) )
其中v就是上述的提前参数fmt,(va_list)&v 是取得已知参数的地址,_INTSIZEOF(v)是已知参数的偏移量,
所以ap 就是第一个未知参数地址,
va_arg()替换后,ap指向t类型后的下一个类型的地址。va_arg本身被替换成了t类型对应参数的值. t类型一般是从fmt格式化串中根据%d,%c等转换的.
比如1: int a = va_art(ap, int);
比如2: fun(int n,...)
{
...
char* pChar = va_arg(ap, char*)
int tmp = va_arg(ap, int)
double dd = va_arg(ap, double)
}
那么,...必然是 char*,int,double的参数顺序!!如果不是这样的顺序,程序将不定期崩溃!!这就是为什么c不是类型安全的,boost::Format好像提供类型安全的格式转换,可以替代sprintf的使用。
(3)va_end只是简单地将指针置零
第四:小结
1.标准C库的中的三个宏的作用只是用来确定可变参数列表中每个参数的内存地址,编译器是不知道参数的实际数目的。
2.在实际应用的代码中,程序员必须自己考虑确定参数数目的办法
1)在固定参数设定标志,例如printf的实现,有多少个%号则表明后面多少个参数
2)多设置一个可变参数标识参数列表结束
3.实现可变参数的要点就是想办法取得每个参数的地址,取得地址的办法由以下几个因素决定:
①函数栈的生长方向
②参数的入栈顺序
③CPU的对齐方式
④内存地址的表达方式,用void*表示一般地址还是char*
结合源代码,我们可以看出va_list的实现是由④决定的,_INTSIZEOF(n)的引入则是由③决定的,他和①②又一起决定了va_start的实现,最后va_end的存在则是良好编程风格的体现,将不再使用的指针设为NULL,这样可以防止以后的误操作。
#include "stdlib.h"
void myprintf(char* fmt, ...) //一个简单的类似于printf的实现,//参数必须都是int 类型
{
char* pArg=NULL; //等价于原来的va_list
char c;
pArg = (char*) &fmt; //注意不要写成p = fmt !!因为这里要对//参数取址,而不是取值
pArg += sizeof(fmt); //等价于原来的va_start
do
{
c =*fmt;
if (c != '%')
{
putchar(c); //照原样输出字符
}
else
{
//按格式字符输出数据
switch(*++fmt)
{
case'd':
printf("%d",*((int*)pArg));
break;
case'x':
printf("%#x",*((int*)pArg));
break;
default:
break;
}
pArg += sizeof(int); //等价于原来的va_arg
}
++fmt;
}while (*fmt != '/0');
pArg = NULL; //等价于va_end
return;
}
int main(int argc, char* argv[])
{
int i = 1234;
int j = 5678;
myprintf("the first test:i=%d/n",i,j);
myprintf("the secend test:i=%d; %x;j=%d;/n",i,0xabcd,j);
system("pause");
return 0;
}
参考:http://www.cnblogs.com/hnrainll/archive/2011/08/05/2128496.html