欢迎来到天天文库
浏览记录
ID:8840117
大小:61.50 KB
页数:8页
时间:2018-04-09
《详解c语言可变参数valist和vsnprintf及printf实现》由会员上传分享,免费在线阅读,更多相关内容在应用文档-天天文库。
1、C语言的变长参数在平时做开发时很少会在自己设计的接口中用到,但我们最常用的接口printf就是使用的变长参数接口,在感受到printf强大的魅力的同时,是否想挖据一下到底printf是如何实现的呢?这里我们一起来挖掘一下C语言变长参数的奥秘。先考虑这样一个问题:如果我们不使用C标准库(libc)中提供的Facilities,我们自己是否可以实现拥有变长参数的函数呢?我们不妨试试。一步一步进入正题,我们先看看固定参数列表函数,voidfixed_args_func(inta,doubleb,char*c){ pr
2、intf("a=0x%p",&a); printf("b=0x%p",&b); printf("c=0x%p",&c);}对于固定参数列表的函数,每个参数的名称、类型都是直接可见的,他们的地址也都是可以直接得到的,比如:通过&a我们可以得到a的地址,并通过函数原型声明了解到a是int类型的;通过&b我们可以得到b的地址,并通过函数原型声明了解到b是double类型的;通过&c我们可以得到c的地址,并通过函数原型声明了解到c是char*类型的。但是对于变长参数的函数,我们就没有这么顺利了。
3、还好,按照C标准的说明,支持变长参数的函数在原型声明中,必须有至少一个最左固定参数(这一点与传统C有区别,传统C允许不带任何固定参数的纯变长参数函数),这样我们可以得到其中固定参数的地址,但是依然无法从声明中得到其他变长参数的地址,比如:voidvar_args_func(constchar*fmt,...){ ......}这里我们只能得到fmt这固定参数的地址,仅从函数原型我们是无法确定"..."中有几个参数、参数都是什么类型的,自然也就无法确定其位置了。那么如何可以做到呢?在大脑中回想一下函数传参的过程,无论".
4、.."中有多少个参数、每个参数是什么类型的,它们都和固定参数的传参过程是一样的,简单来讲都是栈操作,而栈这个东西对我们是开放的。这样一来,一旦我们知道某函数帧的栈上的一个固定参数的位置,我们完全有可能推导出其他变长参数的位置,顺着这个思路,我们继续往下走,通过一个例子来诠释一下:(这里要说明的是:函数参数进栈以及参数空间地址分配都是"实现相关"的,不同平台、不同编译器都可能不同,所以下面的例子仅在IA-32,WindowsXP,MinGWgccv3.4.2下成立)我们先用上面的那个fixed_args_func函数确定一下
5、这个平台下的入栈顺序。intmain(){ fixed_args_func(17,5.40,"helloworld"); return0;}a=0x0022FF50b=0x0022FF54c=0x0022FF5C从这个结果来看,显然参数是从右到左,逐一压入栈中的(栈的延伸方向是从高地址到低地址,栈底的占领着最高内存地址,先入栈的参数,其地理位置也就最高了)。我们基本可以得出这样一个结论: c.addr=b.addr+x_sizeof(b); /*注意: x_sizeof!=sizeof,后话再说*/ b.addr=
6、a.addr+x_sizeof(a);有了以上的"等式",我们似乎可以推导出voidvar_args_func(constchar*fmt,...)函数中,可变参数的位置了。起码第一个可变参数的位置应该是:first_vararg.addr=fmt.addr+x_sizeof(fmt); 根据这一结论我们试着实现一个支持可变参数的函数:voidvar_args_func(constchar*fmt,...){ char *ap; ap=((char*)&fmt)+sizeof(fmt); printf("%
7、d",*(int*)ap); ap= ap+sizeof(int); printf("%d",*(int*)ap); ap= ap+sizeof(int); printf("%s",*((char**)ap));}intmain(){ var_args_func("%d%d%s",4,5,"helloworld");}输出结果:45helloworldvar_args_func只是为了演示,并未根据fmt消息中的格式字符串来判断变参的个数和类型,而是直接在实现中写死了,如果
8、你把这个程序拿到solaris9下,运行后,一定得不到正确的结果,为什么呢,后续再说。先来解释一下这个程序。我们用ap获取第一个变参的地址,我们知道第一个变参是4,一个int型,所以我们用(int*)ap以告诉编译器,以ap为首地址的那块内存我们要将之视为一个整型来使用,*(int*)ap获得该参数的值
此文档下载收益归作者所有