浅谈C语言的可变参数

  C语言中有些函数使用可变参数,比如常见的int printf( const char* format, ...),第一个参数format是固定的,其余的参数的个数和类型都不固定。

  C语言用va_start等宏来处理这些可变参数。这些宏看起来很复杂,其实原理挺简单,就是根据参数入栈的特点从最靠近第一个可变参数的固定参数开始,依次获取每个可变参数的地址。下面我们来分析这些宏。

  在stdarg.h头文件中,针对不同平台有不同的宏定义,我们选取X86平台下的宏定义:

typedef char *  va_list;

#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(t)) )

#define va_end(ap)      ( ap = (va_list)0 )

 

  _INTSIZEOF(n)宏是为了考虑那些内存地址需要对齐的系统,从宏的名字来应该是跟sizeof(int)对齐。一般的sizeof(int)=4,也就是参数在内存中的地址都为4的倍数。比如,如果sizeof(n)14之间,那么_INTSIZEOF(n)4;如果sizeof(n)58之间,那么_INTSIZEOF(n)=8

  为了能从固定参数依次得到每个可变参数,va_start,va_arg充分利用下面两点:

  1. C语言在函数调用时,先将最后一个参数压入栈

  2. X86平台下的内存分配顺序是从高地址内存到低地址内存

  高位地址

    第N个可变参数

    。。。

    第二个可变参数

    第一个可变参数      ß ap

    固定参数           ß v

  低位地址

  由上图可见,v是固定参数在内存中的地址,在调用va_start后,ap指向第一个可变参数。这个宏的作用就是在v的内存地址上增加v所占的内存大小,这样就得到了第一个可变参数的地址。

  接下来,可以这样设想,如果我能确定这个可变参数的类型,那么我就知道了它占用了多少内存,依葫芦画瓢,我就能得到下一个可变参数的地址。

  让我再来看看va_arg,它先ap指向下一个可变参数,然后减去当前可变参数的大小即得到当前可变参数的内存地址,再做个类型转换,返回它的值。

  要确定每个可变参数的类型,有两种做法,要么都是默认的类型,要么就在固定参数中包含足够的信息让程序可以确定每个可变参数的类型。比如,printf,程序通过分析format字符串就可以确定每个可变参数的类型。

  最后一个宏就简单了,va_end使得ap不再指向有效的内存地址。

  看了这几个宏,不禁让我再次感慨,C语言太灵活了,而且代码可以写得非常简洁,虽然有时候让人看得不是很明白,但是一旦明白 过来,你肯定会为它击掌叫好!

  其实在varargs.h头文件中定义了UNIX System V形式的va系列宏,而上面在stdarg.h头文件中定义的是ANSI C形式的宏,这两种宏是不兼容的,一般说来,我们应该使用ANSI C形式的va宏。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值