可变参数的函数和可变参数宏

我们对现实生活中很多事情已习以为常,比如娱乐圈的潜规则,中国足球的假球,土的掉渣的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)&+ _INTSIZEOF(v) ) //第一个可选参数地址
  3. #define va_arg(ap,t) ( *(*)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) ) //下一个参数地址
  4. #define va_end(ap) ( ap = (va_list)) // 将指针置为无效
  5. 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)
      • {
      •     simple_va_fun(100);
      •     simple_va_fun(100,200);
      •     simple_va_fun(100,200,300);
      •     return 0;

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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值