zoukankan      html  css  js  c++  java
  • 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;
    }
    
  • 相关阅读:
    Linux下通过二进制方式安装mysql5.7版本和系统优化
    SQL中的real、float、decimal、numeric数据类型区别
    SQL中的事务ACID
    一台服务器搭建部署两个或多个Redis实例
    SQLServer数据库镜像高性能模式下维护
    SQLServer配置镜像,无法将 ALTER DATABASE 命令发送到远程服务器实例,数据库镜像配置未更改。请确保该服务器已连接,然后重试。
    阿里云数据库MongoDB版清理oplog日志和compact命令详解
    Linux下shell脚本实现mongodb定时自动备份
    List分组
    Sql Server日期查询-SQL查询今天、昨天、7天内、30天
  • 原文地址:https://www.cnblogs.com/wucg/p/2368354.html
Copyright © 2011-2022 走看看