东黑布衣,流浪幽燕。 真诚善良,值得信赖。
全部博文(327)
分类: 系统运维
2006-08-08 14:38:28
C语言中有些函数使用可变参数,比如常见的int printf( const char* format, ...),第一个参数format是固定的,其余的参数的个数和类型都不固定。
C语言用根据参数入栈的特点从(最靠近第一个可变参数的)固定参数开始,依次获取每个可变参数的地址。
在stdarg.h头文件中,针对不同平台有不同的宏定义,X86平台下的宏定义如下:
_INTSIZEOF(n)宏是为了考虑那些内存地址需要对齐的系统。
#define _INTSIZEOF(n) ((sizeof(n)+sizeof(int)-1)&~(sizeof(int) - 1) )
首先sizeof(int)肯定是2的次方数,比如32位是4,64位是8
这样sizeof(int)写成2进制是1后面若干个0,假设是n个0
sizeof(int) - 1 就是n个1,
取非再和前面的数取与就是清除掉前面那个数的后面n比特。
如果sizeof(n) 后面n比特都是0,那么加sizeof(int)-1不进位,相当于加上再清除掉。
如果sizeof(n) 后面n比特至少有1位是1,那么加了之后会往前进1,相当于sizeof(n) 右移
n位,加1,再补充上n个0。
为了能从固定参数依次得到每个可变参数,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不再指向有效的内存地址。
在varargs.h头文件中定义了UNIX System V实行的va系列宏,而上面在stdarg.h头文件中定义的是ANSI C形式的宏,这两种宏不兼容,一般我们使用ANSI C形式的va宏。