C语言可变参数,va_arg、va_list,va_start,va_end,_INTSIZEOF浅析
学习C语言可变参数时,发现
#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
乍一看,完全不明白该宏的作用是啥,仔细分析后发现,该宏是求类型n是整型int的多少倍(向上取整).
在32位win中,sizeof(int)=4 。
如果n为char,一字节,则sizeof(n)= (1+3)&~3=4;
若n为short,sizeof(n)=(2+3)&~3=4;
若n为double,sizeof(n)=(8+3)&~3=8;
该宏的效果等价于:
#define _MY_INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) / (sizeof(int)) * (sizeof(int)) )
基本思想是:让类型n的长度加上整型长度-1,为了保证与整型长度相除向上取到整数倍,再乘以整型长度,这样就得出
以整型来存储该类型需要多少字节,目的就是为了传可变个数参数时,内存对齐。
/ (sizeof(int)) * (sizeof(int)) ) 可以优化的地方在于,int为2的n次方个字,比如16位的win中,int为了2Byte(2^1),
32位的win中,整型长度为4(2^2)。以二进制的观点,除以2^n相当于右移两n位,乘以2^n相当于左移n位。
则/ (sizeof(int)) * (sizeof(int)) ) 相当于右移n位再左移n位,也就可以说让它低n位都置0即可。
& ~(sizeof(int) - 1) 就可以达到该效果,以sizeof(int)=4为例,~(sizeof(int)-1)=~3=(1111 1111 1111 1100)b
该数再与前面的数进行按位与操作,可将低2位置0。
想到这一步,不得不佩服写出这句程序的人高明之处!
接着看va_list,它是定义成char*,因为c/cpp没有byte类型。
va_start(ap,v); 运行这句后,ap指向第一个参数的地址。调试时发现sum(num,...)几个参数都是顺序存储,
ap= v的地址加上v所占的字段数,故ap指向num后的第一个参数。
再看va_arg,#define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
这一句也比较复杂,思考了许久,开始想不明白为什么要加_INTSIZEOF(t),又减_INTSIZEOF(t),它又是如何递增
指向下一个参数。原来它是先将 ap+=_INTSIZEOF(t),修 改ap的值,此时ap指向下一个参数,再减去
_INTSIZEOF(t),又得到原来的地址,将其返回,转化为类型*的指针,再取指针所指地址的值。这也是处理得很巧妙的地方。
最后va_end( argptr );将指针argptr置为0;
结语:
可变参数实现方法是将,传过去的值或指针封用一个指针去遍历。
PS:
先理解Intel CPU的栈内存是从高地址到低地址增长,在__cdel调用约定中,参数从右往左依次入栈,所以最前面的参数地址越小。找到可变参数前的第一个参数,加上它所占的整数倍内存,把ap定位到可变参数的第一个,取出参数值,并增加相应的栈内存偏移,转到下一个参数,依次读取所有参数。另,C调用约定是由函数调用者恢复栈顶指针ESP,因为由于参数个数的不确定性,在函数内是无法判断多少栈内存偏移的。这也是__stdcall调用约定不支持可变参数的原因,它是由被调用者恢复栈顶指针,因而它的参数个数及类型必须是确定的。
#include <stdio.h> #include <stdarg.h> int sum( int num, ... ) { int answer = 0; /* typedef char * va_list; */ va_list argptr; /* #define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) ) #define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) ) */ va_start( argptr, num ); for( ; num > 0; num-- ) { //#define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) ) answer += va_arg( argptr, int ); } //#define va_end(ap) ( ap = (va_list)0 ) va_end( argptr ); return( answer ); } void my_printf( char *format, ... ) { va_list argptr; va_start( argptr, format ); while( *format != '\0' ) { // string if( *format == 's' ) { char* s = va_arg( argptr, char * ); printf( "Printing a string: %s\n", s ); } // character else if( *format == 'c' ) { char c = (char) va_arg( argptr, int ); printf( "Printing a character: %c\n", c ); break; } // integer else if( *format == 'd' ) { int d = va_arg( argptr, int ); printf( "Printing an integer: %d\n", d ); } format++; } va_end( argptr ); } #pragma pack(push, 1) struct PackedStructure { char a[3]; int b; short c; }; #pragma pack(pop) //sizeof(PackedStructure) : 9 #define _MY_INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) / (sizeof(int)) * (sizeof(int)) ) int main(){ printf("sizeof(char*):%d\n",sizeof(char*)); int answer = sum( 4, 4, 3, 2, 1 ); printf( "The answer is %d\n", answer ); my_printf( "sdc", "This is a string", 29, 'X' ); printf("\nsizeof(int):%d\n",sizeof(int)); printf("sizeof(PackedStructure):%d\n",sizeof(PackedStructure)); printf("_INTSIZEOF(PackedStructure):%d\n",_INTSIZEOF(PackedStructure)); printf("_INTSIEEOF(char)",_INTSIZEOF(char)); printf("\nsizeof(double):%d\n",sizeof(double)); printf("_INTSIZEOF(double):%d\n",_INTSIZEOF(double)); printf("_MY_INTSIZEOF(double):%d\n",_MY_INTSIZEOF(double)); return 0; }