为了正常的体验网站,请在浏览器设置里面开启Javascript功能!

从printf谈可变参数函数的实现2008

2011-01-07 5页 doc 43KB 26阅读

用户头像

is_203157

暂无简介

举报
从printf谈可变参数函数的实现2008从printf谈可变参数函数的实现2008-06-16 22:53作者:戎亚新 从printf谈可变参数函数的实现2008-06-16 22:53作者:戎亚新 摘要:一直以来都觉得printf似乎是c语言库中功能最强大的函数之一,不仅因为它能格式化输出,更在于它的参数个数没有限制,要几个就给几个,来者不拒。printf这种对参数个数和参数类型的强大适应性,让人产生了对它进行探索的浓厚兴趣。 关键字:printf, 可变参数 1. 使用情形 int a =10; double b = 20.0; char *str = "Hel...
从printf谈可变参数函数的实现2008
从printf谈可变参数函数的实现2008-06-16 22:53作者:戎亚新 从printf谈可变参数函数的实现2008-06-16 22:53作者:戎亚新 摘要:一直以来都觉得printf似乎是c语言库中功能最强大的函数之一,不仅因为它能格式化输出,更在于它的参数个数没有限制,要几个就给几个,来者不拒。printf这种对参数个数和参数类型的强大适应性,让人产生了对它进行探索的浓厚兴趣。 关键字:printf, 可变参数 1. 使用情形 int a =10; double b = 20.0; char *str = "Hello world"; printf("begin print\n"); printf("a=%d, b=%.3f, str=%s\n", a, b, str); ...   从printf的使用情况来看,我们不难发现一个规律,就是无论其可变的参数有多少个,printf的第一个参数总是一个字符串。而正是这第一个参数,使得它可以确认后面还有有多少个参数尾随。而尾随的每个参数占用的栈空间大小又是通过第一个格式字符串确定的。然而printf到底是怎样取第一个参数后面的参数值的呢,请看如下代码 2. printf 函数的实现 //acenv.h typedef char *va_list; #define _AUPBND (sizeof (acpi_native_int) - 1) #define _ADNBND (sizeof (acpi_native_int) - 1) #define _bnd(X, bnd) (((sizeof (X)) + (bnd)) & (~(bnd))) #define va_arg(ap, T) (*(T *)(((ap) += (_bnd (T, _AUPBND))) - (_bnd (T,_ADNBND)))) #define va_end(ap) (void) 0 #define va_start(ap, A) (void) ((ap) = (((char *) &(A)) + (_bnd (A,_AUPBND)))) //start.c static char sprint_buf[1024]; int printf(char *fmt, ...) { va_list args; int n; va_start(args, fmt); n = vsprintf(sprint_buf, fmt, args); va_end(args); write(stdout, sprint_buf, n); return n; } //unistd.h static inline long write(int fd, const char *buf, off_t count) { return sys_write(fd, buf, count); } 3.   从上面的代码来看,printf似乎并不复杂,它通过一个宏va_start把所有的可变参数放到了由args指向的一块内存中,然后再调用vsprintf. 真正的参数个数以及格式的确定是在vsprintf搞定的了。由于vsprintf的代码比较复杂,也不是我们这里要讨论的重点,所以下面就不再列出了。我们这里要讨论的重点是va_start(ap, A)宏的实现,它对定位从参数A后面的参数有重大的制导意义。现在把 #define va_start(ap, A) (void) ((ap) = (((char *) &(A)) + (_bnd (A,_AUPBND)))) 的含义解释一下如下: va_start(ap, A) { char *ap = ((char *)(&A)) + sizeof(A)并int类型大小地址对齐 }   在printf的va_start(args, fmt)中,fmt的类型为char *, 因此对于一个32为系统 sizeof(char *) = 4, 如果int大小也是32,则va_start(args, fmt);相当于 char *args = (char *)(&fmt) + 4; 此时args的值正好为fmt后第一个参数的地址。对于如下的可变参数函数 void fun(double d,...) { va_list args; int n; va_start(args, d); } 则 va_start(args, d);相当于 char *args = (char *)&d + sizeof(double);   此时args正好指向d后面的第一个参数。   可变参数函数的实现与函数调用的栈结构有关,正常情况下c/c++的函数参数入栈规则为__stdcall, 它是从右到左的,即函数中的最右边的参数最先入栈。对于函数 void fun(int a, int b, int c) { int d; ... } 其栈结构为 0x1ffc-->d 0x2000-->a 0x2004-->b 0x2008-->c   对于任何编译器,每个栈单元的大小都是sizeof(int), 而函数的每个参数都至少要占一个栈单元大小,如函数 void fun1(char a, int b, double c, short d) 对一个32的系统其栈的结构就是 0x1ffc-->a (4字节) 0x2000-->b (4字节) 0x2004-->c (8字节) 0x200c-->d (4字节)   对于函数void fun1(char a, int b, double c, short d)   如果知道了参数a的地址,则要取后续参数的值则可以通过a的地址计算a后面参数的地址,然后取对应的值,而后面参数的个数可以直接由变量a指定,当然也可以像printf一样根据第一个参数中的%模式个数来决定后续参数的个数和类型。如果参数的个数由第一个参数a直接决定,则后续参数的类型如果没有变化并且是已知的,则我们可以这样来取后续参数, 假定后续参数的类型都是double; void fun1(int num, ...) { double *p = (double *)((&num)+1); double Param1 = *p; double Param2 = *(p+1); ... double Paramn *(p+num); }   如果后续参数的类型是变化而且是未知的,则必须通过一个参数中设定模式来匹配后续参数的个数和类型,就像printf一样,当然我们可以定义自己的模式,如可以用i表示int参数,d表示double参数,为了简单,我们用一个字符表示一个参数,并由该字符的名称决定参数的类型而字符的出现的顺序也表示后续参数的顺序。 我们可以这样定义字符和参数类型的映射表, i---int s---signed short l---long c---char "ild"模式用于表示后续有三个参数,按顺序分别为int, long, double类型的三个参数那么这样我们可以定义自己版本的printf 如下 void printf(char *fmt, ...) { char s[80] = ""; int paramCount = strlen(fmt); write(stdout, "paramCount = " , strlen(paramCount = )); itoa(paramCount,s,10); write(stdout, s, strlen(s)); char *p = (char *)(&fmt) + sizeof(char *); int *pi = (int *)p; for (int i=0; i
/
本文档为【从printf谈可变参数函数的实现2008】,请使用软件OFFICE或WPS软件打开。作品中的文字与图均可以修改和编辑, 图片更改请在作品中右键图片并更换,文字修改请直接点击文字进行修改,也可以新增和删除文档中的内容。
[版权声明] 本站所有资料为用户分享产生,若发现您的权利被侵害,请联系客服邮件isharekefu@iask.cn,我们尽快处理。 本作品所展示的图片、画像、字体、音乐的版权可能需版权方额外授权,请谨慎使用。 网站提供的党政主题相关内容(国旗、国徽、党徽..)目的在于配合国家政策宣传,仅限个人学习分享使用,禁止用于任何广告和商用目的。

历史搜索

    清空历史搜索