详解-C语言可变参数-va-list和-vsnprintf及printf实现.pdf
《详解-C语言可变参数-va-list和-vsnprintf及printf实现.pdf》由会员分享,可在线阅读,更多相关《详解-C语言可变参数-va-list和-vsnprintf及printf实现.pdf(7页珍藏版)》请在淘文阁 - 分享文档赚钱的网站上搜索。
1、-C 语言的变长参数在平时做开发时很少会在自己设计的接口中用到,但我们最常用的接口printf 就是使用的变长参数接口,在感受到printf 强大的魅力的同时,是否想挖据一下到底prntf 是如何实现的呢?这里我们一起来挖掘一下C 语言变长参数的奥秘。先考虑这样一个问题:如果我们不使用标准库(libc)中提供的 Faclitis,我们自己是否可以实现拥有变长参数的函数呢?我们不妨试试。一步一步进入正题,我们先看看固定参数列表函数,void ied_rgs_fun(i a,ouleb,har c)pint(=0%pn,a);printf(b=0 x%pn,&b);rtf(c=0%pn,&c);对
2、于固定参数列表的函数,每个参数的名称、类型都是直接可见的,他们的地址也都是可以直接得到的,比如:通过我们可以得到的地址,并通过函数原型声明了解到a 是n类型的;通过&b 我们可以得到 b 的地址,并通过函数原型声明了解到是doble 类型的;通过&我们可以得到的地址,并通过函数原型声明了解到是har类型的。但是对于变长参数的函数,我们就没有这么顺利了。还好,按照 C 标准的说明,支持变长参数的函数在原型声明中,必须有至少一个最左固定参数(这一点与传统 C 有区别,传统允许不带任何固定参数的纯变长参数函数),这样我们可以得到其中固定参数的地址,但是依然无法从声明中得到其他变长参数的地址,比如:v
3、oidvrag_unc(const hr*mt,.).这里我们只能得到 fmt 这固定参数的地址,仅从函数原型我们是无法确定.中有几个参数、参数都是什么类型的,自然也就无法确定其位置了。那么如何可以做到呢?在大脑中回想一下函数传参的过程,无论.中有多少个参数、每个参数是什么类型的,它们都和固定参数的传参过程是一样的,简单来讲都是栈操作,而栈这个东西对我们是开放的。这样一来,一旦我们知道某函数帧的栈上的一个固定参数的位置,我们完全有可能推导出其他变长参数的位置,顺着这个思路,我们继续往下走,通过一个例子来诠释一下:(这里要说明的是:函数参数进栈以及参数空间地址分配都是实现相关的,不同平台、不同编
4、译器都可能不同,所以下面的例子仅在 IA-32,WidowsXP,iG cc v3.4.2 下成立)我们先用上面的那个ixedargs_unc 函数确定一下这个平台下的入栈顺序。int main()ixedarsfunc(17,54,eloworld);return 0;a=x022F50b=0 x0022FF54c=0002FFC从这个结果来看,显然参数是从右到左,逐一压入栈中的(栈的延伸方向是从高地址到低地址,栈底的占领着最高内存地址,先入栈的参数,其地理位置也就最高了)。我们基本可以得出这样一个结论:c.adr=b.addr+sizef(b);/*注意:x_ieof!=izef,后话再说
5、/b.add=a.ar x_sizeof(a);有了以上的等式,我们似乎可以推导出 oid ar_ars_unc(con cha*fmt,.)函数中,可变参数的位置了。起码第一个可变参数的位置应该是:first_varargadr=ft.addr+_sizof(t);根据这一结论我们试着实现一个支持可变参数的函数:voiva_rg_func(cos a*fm,.)-cha*a;a=(a*)&fmt)izof(fmt);pintf(dn,(nt*)ap);ap=a+sizeo(int);pritf(%dn,*(i*)a);ap=ap+izf(int);ptf(%s,*((char*)a);int
6、main()rarg_unc(%dd%,4,5,lo ld);输出结果:4hell orldar_ags_fuc 只是为了演示,并未根据 ft 消息中的格式字符串来判断变参的个数和类型,而是直接在实现中写死了,如果你把这个程序拿到saris 9 下,运行后,一定得不到正确的结果,为什么呢,后续再说。先来解释一下这个程序。我们用 ap 获取第一个变参的地址,我们知道第一个变参是 4,一个 in型,所以我们用(int*)a以告诉编译器,以 a为首地址的那块内存我们要将之视为一个整型来使用,(it*)ap获得该参数的值;接下来的变参是5,又一个 int 型,其地址是ap+sizeo(第一个变参),也
7、就是 ap+sizeo(n),同样我们使用*(in*)ap 获得该参数的值;最后的一个参数是一个字符串,也就是har*,与前两个 it 型参数不同的是,经过p+izef(in)后,ap 指向栈上一个ha*类型的内存块(我们暂且称之 tp_pr,chtmpptr)的首地址,即 ap m_tr,而我们要输出的不是 printf(%sn,a),而是 printf(s,tm_ptr);print(%sn,ap)是意图将 ap 所指的内存块作为字符串输出了,但是&m_ptr,tmp_pr 所占据的4 个字节显然不是字符串,而是一个地址。如何让_pr 是 ch*类型的,我们将p 进行强制转换(car*)a
8、p&tmp_ptr,这样我们访问tmppr 只需要在(char*)p 前面加上一个*即可,即 prn(sn,*(car*)ap);前面说过,如果将var_agsfnc 放到 sls 上,一定是得不到正确结果的?为什么呢?由于内存对齐。编译器在栈上压入参数时,不是一个紧挨着另一个的,编译器会根据变参的类型将其放到满足类型对齐的地址上的,这样栈上参数之间实际上可能会是有空隙的。上述例子中,我是根据反编译后的汇编码得到的参数间隔,还好都是 4,然后在代码中写死了。为了满足代码的可移植性,C 标准库在 sdar.h 中提供了诸多ailties 以供实现变长长度参数时使用。这里也列出一个简单的例子,看看
9、利用标准库是如何支持变长参数的:#nclue void d_arg_fun(cons har*t,.)va_l ap;_start(a,fmt);printf(%dn,va_arg(p,in);rintf(%fn,va_g(a,dube);prinf(%,va_rg(p,chr*));va_end(p);it ma()svaargfunc(%f%,4,5.,llowold);输出:4-5.400000llo wold对比一下 s_varagunc 和ar_arg_func 的实现,va_list 似乎就是 char*,va_tar似乎就是((char*)&fm)+eo(m),va_r似乎就是得
10、到下一个参数的首地址。没错,多数平台下 stdar.中 valst,va_start 和 va_ar的实现就是类似这样的。一般arg.h 会包含很多宏,看起来比较复杂。在有的系统中stdarg.h 的实现依赖some sial nctionbil in the the comlaonsstem o hadle vai arument lits an s allocations,多数其他系统的实现与下面很相似:(Visal+6 0 的实现较为清晰,因为indows 上的应用程序只需要在winows平台间做移植即可,没有必要考虑太多的平台情况)。C 语言 va_lis与vnprnf 的使用先举一个
11、例子先举一个例子:#define bufsiz 0ca buferbufsie;/这个函数用来格式化带参数的字符串*/int vsf(char*fmt,.)vlst rg;/声明一个转换参数的变量it cnt;va_srt(rgpt,f);/初始化变量ct=vsnprnf(bufer,ufsze,fmt,argptr);/将带参数的字符串按照参数列表格式化到bufer中va_end(arptr);/结束变量列表,和 va_stat 成对使用retun(nt);int ai(i arg,cragv)i inmb=;float fnmber 0.0;hr sing4=abc;sp(%d%f%s,n
12、umbe,fumbe,t);prn(%s,bff);rturn 0;下面我们来探讨如何写一个简单的可变参数的C 函数写可变参数的函数要在程序中用到以下这些宏:写可变参数的函数要在程序中用到以下这些宏:使用可变参数应该有以下步骤:1)首先在函数里定义一个va_lis型的变量,这里是 ar_ptr,这个变量是指向参数的指针.2)然后用a_sr宏初始化变量 argp,这个宏的第二个参数是第一个可变参数的前一个参数,是一个固定的参数.3)然后用a_arg 返回可变的参数,并赋值给整数 j aa的第二个参数是你要返回的参数的类型,这里是 int 型.4)最后用and 宏结束可变参数的获取.然后你就可以在
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 详解 语言 可变 参数 va list vsnprintf printf 实现
限制150内