C语言可变参数,va_arg、va_list,va_start,va_end,_INTSIZEOF浅析

时间:2021-01-23 19:39:14

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