Chinaunix首页 | 论坛 | 博客
  • 博客访问: 97533
  • 博文数量: 24
  • 博客积分: 407
  • 博客等级: 一等列兵
  • 技术积分: 291
  • 用 户 组: 普通用户
  • 注册时间: 2010-12-02 08:31
文章分类
文章存档

2012年(24)

我的朋友

分类: C/C++

2012-02-24 15:11:18

我们对现实生活中很多事情已习以为常,比如娱乐圈的潜规则,中国足球的假球,土的掉渣的printf()。殊不知这背后蕴藏着重重玄机,复杂的机制。现在我们就试着即开printf的神秘面纱。
 
学习c之后很多年都没想过printf有那么多不同之处,可变参数函数的实现一点都不简单,就像没有无缘无故的爱,它的实现不是天然的。
 
先看看,c为可变参函数提供的几个利器,以宏的形式实现。
 
  1. #define _INTSIZEOF(n) ((sizeof(n)+sizeof(int)-1)&~(sizeof(int) - 1) )

  2. #define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) ) //第一个可选参数地址
  3. #define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) ) //下一个参数地址
  4. #define va_end(ap) ( ap = (va_list)0 ) // 将指针置为无效

va_list用于定义一个变量获取可变参数指针
va_start用于将va_list定义的指针进行初始化
va_arg用于获取对应指针的真实类型数据
va_end用于清空va_list定义的指针

擒贼先擒王,在这里只解决va_arg,别的不需惧怕。曾经,我看不懂这个。看懂之后感受到了造物主的伟大。

ap+=_INTSIZEOF(t);这里就使得ap指向了下一个可变参数的起始地址。然后

ap+=_INTSIZEOF(t)-_INTSIZEOF(t)整个表达式的结果回到当前这个可变参数的起始地址,但是注意到ap已经指向下一个可变参数地址了。这就方便后续的处理 。

我们有必要了解一下C函数的调用规则了,在调用一个函数之前,调用方会将这个函数参数push(修改ESP指针),并且push规则是先push最后一个参数,最后push第一个参数,因此ESP指针最后应该是指向第一个参数。可变参数就是利用了这一点,一旦获取到第一个参数的地址后,就能够通过地址向前查找所有的参数。(注意:x86上的堆栈是反向的,push会使ESP的值减少,而不是增加)。

看到这里,相信大家应该可以编写一个简单的可变参数函数了。我还是直接copy一个,呵呵,关键时候要的就是稳健,请主宽恕我。

  1. #include<stdio.h>
  2. #include<stdarg.h>

  3. void simple_va_fun(int i, ...)
  4. {va_list arg_ptr;
  5.  int j=0; va_start(arg_ptr, i);
  6.  j=va_arg(arg_ptr, int);
  7.  va_end(arg_ptr);
  8.  printf("%d %d\n", i, j);
  9. }



  10. int main(void)
  11. {
  12.     simple_va_fun(100);
  13.     simple_va_fun(100,200);
  14.     simple_va_fun(100,200,300);
  15.     return 0;

  16. }


 

只有第二个是正确的,其实答案可想而知,这跟我们所写的程序有关。由于没有类型和参数的检查,最多也就只能这样了。那为什么人家的printf便可以辨识呢??那是因为函数printf是从固定参数format字符串来分析出参数的类型,再调用va_arg的来获取可变参数的。这下应该理清楚了。

可变参数宏就不说了吧,简单的画个妆而已,不过依然很强大,呵呵。

 

 

 

 

 

 

 

 

阅读(2310) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~