学习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;
}