c语言可变参数

上传人:正** 文档编号:40964547 上传时间:2018-05-27 格式:DOC 页数:10 大小:31.50KB
返回 下载 相关 举报
c语言可变参数_第1页
第1页 / 共10页
c语言可变参数_第2页
第2页 / 共10页
c语言可变参数_第3页
第3页 / 共10页
c语言可变参数_第4页
第4页 / 共10页
c语言可变参数_第5页
第5页 / 共10页
点击查看更多>>
资源描述

《c语言可变参数》由会员分享,可在线阅读,更多相关《c语言可变参数(10页珍藏版)》请在金锄头文库上搜索。

1、很多技术人员都有在“技术细节“上“钻牛角尖“的“癖好“,对此很多人褒贬不一;无论怎样,我也是属于这类人。C 语言的变长参数在平时做开发时很少会在自己设计的接口中用到,但我们最常用的接口 printf 就是使用的变长参数接口,在感受到printf 强大的魅力的同时,是否想挖据一下到底 printf 是如何实现的呢?这里我们一起来挖掘一下 C 语言变长参数的奥秘。先考虑这样一个问题:如果我们不使用 C 标准库(libc)中提供的 Facilities,我们自己是否可以实现拥有变长参数的函数呢?我们不妨试试。一步一步进入正题,我们先看看固定参数列表函数,void fixed_args_func(in

2、t a, double b, char *c) printf(“a = 0x%pn“, printf(“b = 0x%pn“, printf(“c = 0x%pn“, 对于固定参数列表的函数,每个参数的名称、类型都是直接可见的,他们的地址也都是可以直接得到的,比如:通过 通过 通过return 0;a = 0x0022FF50b = 0x0022FF54c = 0x0022FF5C从这个结果来看,显然参数是从右到左,逐一压入栈中的(栈的延伸方向是从高地址到低地址,栈底的占领着最高内存地址,先入栈的参数,其地理位置也就最高了)。我们基本可以得出这样一个结论:c.addr = b.addr + x

3、_sizeof(b); /*注意: x_sizeof != sizeof,后话再说 */b.addr = a.addr + x_sizeof(a);有了以上的“等式“,我们似乎可以推导出 void var_args_func(const char * fmt, . ) 函数中,可变参数的位置了。起码第一个可变参数的位置应该是:first_vararg.addr = fmt.addr + x_sizeof(fmt); 根据这一结论我们试着实现一个支持可变参数的函数:void var_args_func(const char * fmt, . ) char *ap;ap = (char*)prin

4、tf(“%dn“, *(int*)ap); ap = ap + sizeof(int);printf(“%dn“, *(int*)ap);ap = ap + sizeof(int);printf(“%sn“, *(char*)ap);int main()var_args_func(“%d %d %sn“, 4, 5, “hello world“);输出结果:45hello worldvar_args_func 只是为了演示,并未根据 fmt 消息中的格式字符串来判断变参的个数和类型,而是直接在实现中写死了,如果你把这个程序拿到 solaris 9 下,运行后,一定得不到正确的结果,为什么呢,后

5、续再说。先来解释一下这个程序。我们用 ap 获取第一个变参的地址,我们知道第一个变参是 4,一个 int 型,所以我们用(int*)ap 以告诉编译器,以 ap 为首地址的那块内存我们要将之视为一个整型来使用,*(int*)ap 获得该参数的值;接下来的变参是 5,又一个 int 型,其地址是 ap + sizeof(第一个变参),也就是 ap + sizeof(int),同样我们使用*(int*)ap 获得该参数的值;最后的一个参数是一个字符串,也就是 char*,与前两个 int 型参数不同的是,经过 ap + sizeof(int)后,ap 指向栈上一个 char*类型的内存块(我们暂且

6、称之 tmp_ptr, char *tmp_ptr)的首地址,即 ap - printf(“%sn“, ap)是意图将 ap 所指的内存块作为字符串输出了,但是 ap - 前面说过,如果将 var_args_func 放到 solaris 上,一定是得不到正确结果的?为什么呢?由于内存对齐。编译器在栈上压入参数时,不是一个紧挨着另一个的,编译器会根据变参的类型将其放到满足类型对齐的地址上的,这样栈上参数之间实际上可能会是有空隙的。上述例子中,我是根据反编译后的汇编码得到的参数间隔,还好都是 4,然后在代码中写死了。为了满足代码的可移植性,C 标准库在 stdarg.h 中提供了诸多Facili

7、ties 以供实现变长长度参数时使用。这里也列出一个简单的例子,看看利用标准库是如何支持变长参数的:#include void std_vararg_func(const char *fmt, . ) va_list ap;va_start(ap, fmt);printf(“%dn“, va_arg(ap, int);printf(“%fn“, va_arg(ap, double);printf(“%sn“, va_arg(ap, char*);va_end(ap);int main() std_vararg_func(“%d %f %sn“, 4, 5.4, “hello world“);输

8、出:45.400000hello world对比一下 std_vararg_func 和 var_args_func 的实现,va_list似乎就是 char*, va_start 似乎就是 (char*)#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(

9、t) )#define va_end(ap) ( ap = (va_list)0 )这里有两个地方需要深入挖掘一下:1、#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & (sizeof(int) - 1) )我们这里简化一下这个宏:#define _INTSIZEOF(n) (sizeof(n) + x) & (x)x = sizeof(int) - 1 = 3 = 0000 0000 0000 0011(b)x = 1111 1111 1111 1100(b)当一个数 & (-x)时,得到的值始终是 sizeof(int)的倍数,

10、也就是说_INTSIZEOF(n)的功能是将 n 圆整到 sizeof(int)的倍数上去。sizeof(n) = 1, sizeof(n)+sizeof(int)-1 经过圆整后,一定会是=4 的整数;在其他系统平台上,圆整的目标值有的是 4,有的则是 8,视具体系统而定。2、#define va_arg(ap,t) ( *(t *)(ap += _INTSIZEOF(t) - _INTSIZEOF(t) )其实有了 var_args_func 的实现,这里也就不难理解了。不过这里有一个 trick,很多人一开始肯定对先加上_INTSIZEOF(t),又减去_INTSIZEOF(t)很不理解,其实这里是一点就透的:整个表达式(ap += _INTSIZEOF(t) - _INTSIZEOF(t) 返回的值其实和最初的 ap 所指向的地址是一致的,关键就是在整个表达式被 evaluated 后,ap 却指向了下一个参数的地址了,就这么简单。在 P.J.Plauger 的“The standard C library“一书的第 10 章节中也有对 stdarg 实现的分析,那个版本虽然比较老,但我想应该是现有版本的一个雏形。 历史上的今天:昆明圆通山动物园拾趣 2007-05-072006 梅月靓乐 2006-05-07收藏到:Del.icio.us

展开阅读全文
相关资源
相关搜索

当前位置:首页 > 办公文档 > 其它办公文档

电脑版 |金锄头文库版权所有
经营许可证:蜀ICP备13022795号 | 川公网安备 51140202000112号