C深入理解变长参数列表的底层原理(核心是构造的四个宏)
先看一个按语法规则编写的使用变长参数列表的实例:// Using variable-length argument lists. #include #include #include using namespace std; double average( int, ... ); int main() { double double1 = 37.5; double double2 = 22.5; double double3 = 1.7; double double4 = 10.2; cout << fixed << setprecision( 1 ) << "double1 = " << double1 << " double2 = " << double2 << " double3 = " << double3 << " double4 = " << double4 << endl << setprecision( 3 ) << " The average of double1 and double2 is " << average( 2, double1, double2 ) << " The average of double1, double2, and double3 is " << average( 3, double1, double2, double3 ) << " The average of double1, double2, double3" << " and double4 is " << average( 4, double1, double2, double3, double4 ) << endl; getchar(); return 0; } // calculate average double average( int count, ... ) { double total = 0; va_list list; // 实质是定义了一个指针类型,后续通过这个指针的算术运算(指针移动)去指向每一个参数 va_start( list, count ); // 使用list指向第一个参数 // process variable-length argument list for ( int i = 1; i <= count; i++ ) total += va_arg( list, double ); // list按double长度移动,使用一次即移动一个double类型长度 va_end( list ); // list置NULL return total / count; } /*output: double1 = 37.5 double2 = 22.5 double3 = 1.7 double4 = 10.2 The average of double1 and double2 is 30.000 The average of double1, double2, and double3 is 20.567 The average of double1, double2, double3 and double4 is 17.975 */
不考虑一般化的情况,针对特定情况按上面的注释改写一下函数(没有使用stdarg.h头文件提供的宏,也就是针对情况情况找到每一个参数的地址,并通过指针的算术运算(移动),逐个找到其它参数):// Using variable-length argument lists. #include #include //#include using namespace std; double average( int, ... ); int main() { double double1 = 37.5; double double2 = 22.5; double double3 = 1.7; double double4 = 10.2; cout << fixed << setprecision( 1 ) << "double1 = " << double1 << " double2 = " << double2 << " double3 = " << double3 << " double4 = " << double4 << endl << setprecision( 3 ) << " The average of double1 and double2 is " << average( 2, double1, double2 ) << " The average of double1, double2, and double3 is " << average( 3, double1, double2, double3 ) << " The average of double1, double2, double3" << " and double4 is " << average( 4, double1, double2, double3, double4 ) << endl; getchar(); return 0; } // calculate average double average( int count, ... ) { double total = 0; //va_list list; // 实质是定义了一个指针类型,后续通过这个指针的算术运算(指针移动)去指向每一个参数 char* list; //va_start( list, count ); // 使用list指向第一个参数 list = (char*)&count + sizeof(int); // 要考虑栈指针对齐的问题 // process variable-length argument list for ( int i = 1; i <= count; i++ ){ //va_arg( list, double ); // list按double长度移动,使用一次即移动一个double类型长度 total += *((double*)list); list += sizeof(double); } //va_end( list ); // list置NULL list = NULL; return total / count; } /*output: double1 = 37.5 double2 = 22.5 double3 = 1.7 double4 = 10.2 The average of double1 and double2 is 30.000 The average of double1, double2, and double3 is 20.567 The average of double1, double2, double3 and double4 is 17.975 */
上面考虑的是具体情况,C标准库肯定要写成一般化的形式,同时还要考虑到栈对齐的情况,以及其它各种参数类型的形式。
我们可以看到stdarg.h中对4个宏的定义:typedef char * va_list; #define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) ) #define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) ) #define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) ) #define va_end(ap) ( ap = (va_list)0 )
需要的前置知识:
I 栈对齐,一般是按字长对齐,通常一个字长的字节数等于sizeof(int),不同的平台有不同的字长,如16位系统的字长就是2个字节,sizeof(int)等于2。32位系统的字长就是4个字节,sizeof(int)等于4。54位系统的字长就是8个字节,sizeof(int)等于8。
II 指针加减一个整型值(如n)的算术运算,表示指针的移动或偏移,其移动的步长是指针目标类型的长度。如:char *ch; ch += n; // 其步长为sizeof(char); int *a; a += n; // 其步长为sizeof(int); double *b; b += n; // 其步长为sizeof(double);
typedef char * va_list;
使用char*指针类型,便于指针类型转换时各个指针类型长度偏移的计算,因为char的长度为一个字节。第1个宏
#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
通过位运算实现栈按字长(int长度)对齐。#include int alignFloor(int n,int m) { //return n - n%m; //return n/m*m; return n & ~(m-1); // 位运算同等实现,2^x=m,x为正整数 } int alignCelling(int n,int m)// 栈对齐要向上舍入 { #if 0 if(n%m) return (n+m) - n%m; else return n; #else //return (n+m-1)/m*m; return (n+m-1) & ~(m-1); // 位运算同等实现,2^x=m,x为正整数 #endif } int main() { int arr[]={1,2,3,4,5,6,7,8},m=4; for(int i=0;i<8;i++) printf("%d %d %d ",arr[i],alignFloor(arr[i],m),alignCelling(arr[i],m)); getchar(); return 0; } /* 1 0 4 2 0 4 3 0 4 4 4 4 5 4 8 6 4 8 7 4 8 8 8 8 */
2^x=m,x为正整数
m-1如果用二进制表示,表示低位有x个"1",其它高位都是"0"。
~(m-1)如果用二进制表示,表示低位有x个"0",其它高位都是"0"。
当某个数与~(m-1)做&位运算,x个低位都会置‘0’。
例如使用的是32位平台,sizeof(int) - 1)等于3,其二进制编码为:
0000 0000 0000 0000 0000 0000 0011
~sizeof(int) - 1)等于-4,其二进制编码为:
1111 1111 1111 1111 1111 1111 1100
当某一个数与~sizeof(int) - 1)进行与运算(&)时,其最后两位如果是1会被置0,如果本身是0则不变,而低位的第3位的位置是4,对应sizeof(int)的值。第2个宏
#define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) )
char*类型的指针ap指向变长参数列表前的参数v的下一个参数。
这里的v表示变长参数列表前的参数名,先取值,然后做类型转换,转换为char*类型,其移动步长为1个字节,再加上v的字节数(宏考虑了栈字节对齐)。第3个宏
#define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
指针ap转换为t*类型,并将ap偏移(向前移动)一个步长(sizeof(t),对转换后的ap做解引用。
ap加减同一个数并不是多此一举,虽然整体表达式计算的地址没有发生改变,但ap却产生了副作用(运算符+=),ap指向了下一个参数。
通常我们使用后置++来下移一个数组元素:void test1() { double arr[] = {1,2,3,4,5,6}; int n = sizeof arr / sizeof *arr; double *p = arr; for(int i=0;i
头腾撕逼大战,为什么大多数媒体人都亲腾讯?近来头条和腾讯的撕逼大战一直没有停过,先是朋友圈高层互怼,接着是各自利用各种的媒体资源互黑,抖音被腾讯屏蔽事情,抖音提示腾讯屏蔽自己,再到头条今日推送的多少道文件才能管住网游对少年
盘点捷德奥特曼中的贝利亚融合兽贝利亚融合兽是伏井出矽(斯特鲁姆星人,如果贝利亚不给力量就不能进行融合升华)或贝利亚奥特曼使用贝利亚升华器(贝利亚升华器只有一个)融合升华两个怪兽胶囊和贝利亚奥特曼融合而成。斯卡鲁
滴滴搞事情?公然向多地停车场内车辆贴罚单对广大车主来说,爱车上一言不合就被贴罚单可以说是最为恼火的事。正是如此,使得车主们更为遵章停车,尤其是停在停车场,可是让车主们最为放心的。然而,即便如此,也未能躲过被违章。今日,在
微信封杀是中国互联网公司躲不过的坎儿这两天,王欣,罗永浩,头条一起发布了三款社交产品,其中一款王欣发布的产品马桶刚上线一天多就被微信封杀,这个事情目前成为了热点,也引起了大家的激烈的讨论,有支持微信的,有支持马桶的,
近期的热门事件国家帮我们出了一口恶气!这几天的事件我想大家都知道了,奔驰女维权,视觉中国,996等事件很火,特别是一件接着一件,而且是持续火爆,有点你方唱罢我来登台的样子,不像以前的网络事件,这个火了,那个就没人关注了
侏儒北欧神话中的类人生物侏儒北欧地区写法Dvergr单数Dvergar众数(儒威尔格)Svartlfar(黑精灵)Dkklfar(暗精灵)是许多奇幻作品中虚构生物矮人(Dwarf)的原型。和另一种虚构生物
索尼发布1英寸图像传感器手机XperiaPROI,却只用近60的面积索尼1英寸图像传感器手机XperiaPROI发布被称为能打电话的相机,主摄镜头镜头来自于中国诚瑞光学的1G5P玻塑混合镜头。2021年10月26日,索尼中国宣布推出摄影旗舰微单手机
看了看很多招聘需求,感觉这些公司未来都是要倒闭的这几天浏览某个招聘网站,发现很多招聘的人员都非常的不专业,不专业会导致什么?招聘不到人才,或者找到人才后,不符合公司的要求。其实招聘是一个非常重要的事情,googleCEO拉里佩奇
无助,太难!被无赖美国公司拖款的九十多天一直没想到,有一天,讨债者,这样的词与我会发生关系。在写这篇文章的时候,很多朋友都劝我,不要闹大了,这样搭上自己公司的未来不值得,但是我认为,士可杀不可辱,我未来即使不做这个行业了
光之国第一个堕落入黑暗的奥特战士贝利亚奥特曼剧中设定为昭和系M78星云奥特之星光之国的黑暗奥特曼。在2009年上映的电影宇宙英雄之超银河传说初登场,曾是与奥特之父同时期的战友,在奥特大战争后受到等离子火花塔的力量的诱惑而堕落
爱普生亮相第四届进博会以智慧科技承发展之脉,绿动未来(ChinaIT。com讯)第四届中国国际进口博览会(以下简称进博会)如约在上海国家会展中心隆重召开,作为世界上第一个以进口为主题的国家级博览盛会,在大力推动大循环双循环的新发展格