先看一个按语法规则编写的使用变长参数列表的实例:Usingvariablelengthargumentlists。doubleaverage(int,。。。);intmain(){doubledouble137。5;doubledouble222。5;doubledouble31。7;doubledouble410。2;coutfixedsetprecision(1)double1double1double2double2double3double3double4double4endlsetprecision(3)Theaverageofdouble1anddouble2isaverage(2,double1,double2)Theaverageofdouble1,double2,anddouble3isaverage(3,double1,double2,double3)Theaverageofdouble1,double2,double3anddouble4isaverage(4,double1,double2,double3,double4)getchar();return0;}calculateaveragedoubleaverage(intcount,。。。){doubletotal0;实质是定义了一个指针类型,后续通过这个指针的算术运算(指针移动)去指向每一个参数vastart(list,count);使用list指向第一个参数processvariablelengthargumentlistfor(inti1;i)totalvaarg(list,double);list按double长度移动,使用一次即移动一个double类型长度vaend(list);list置NULL}output:double137。5double222。5double31。7double410。2Theaverageofdouble1anddouble2is30。000Theaverageofdouble1,double2,anddouble3is20。567Theaverageofdouble1,double2,double3anddouble4is17。975 不考虑一般化的情况,针对特定情况按上面的注释改写一下函数(没有使用stdarg。h头文件提供的宏,也就是针对情况情况找到每一个参数的地址,并通过指针的算术运算(移动),逐个找到其它参数):Usingvariablelengthargumentlists。doubleaverage(int,。。。);intmain(){doubledouble137。5;doubledouble222。5;doubledouble31。7;doubledouble410。2;coutfixedsetprecision(1)double1double1double2double2double3double3double4double4endlsetprecision(3)Theaverageofdouble1anddouble2isaverage(2,double1,double2)Theaverageofdouble1,double2,anddouble3isaverage(3,double1,double2,double3)Theaverageofdouble1,double2,double3anddouble4isaverage(4,double1,double2,double3,double4)getchar();return0;}calculateaveragedoubleaverage(intcount,。。。){doubletotal0;实质是定义了一个指针类型,后续通过这个指针的算术运算(指针移动)去指向每一个参数vastart(list,count);使用list指向第一个参数list(char)countsizeof(int);要考虑栈指针对齐的问题processvariablelengthargumentlistfor(inti1;i){vaarg(list,double);list按double长度移动,使用一次即移动一个double类型长度total((double)list);listsizeof(double);}vaend(list);list置NULLlistNULL;}output:double137。5double222。5double31。7double410。2Theaverageofdouble1anddouble2is30。000Theaverageofdouble1,double2,anddouble3is20。567Theaverageofdouble1,double2,double3anddouble4is17。975 上面考虑的是具体情况,C标准库肯定要写成一般化的形式,同时还要考虑到栈对齐的情况,以及其它各种参数类型的形式。 我们可以看到stdarg。h中对4个宏的定义:defineINTSIZEOF(n)((sizeof(n)sizeof(int)1)(sizeof(int)1))definevastart(ap,v)(ap(valist)vINTSIZEOF(v))definevaarg(ap,t)((t)((apINTSIZEOF(t))INTSIZEOF(t)))definevaend(ap)(ap(valist)0) 需要的前置知识: I栈对齐,一般是按字长对齐,通常一个字长的字节数等于sizeof(int),不同的平台有不同的字长,如16位系统的字长就是2个字节,sizeof(int)等于2。32位系统的字长就是4个字节,sizeof(int)等于4。54位系统的字长就是8个字节,sizeof(int)等于8。 II指针加减一个整型值(如n)的算术运算,表示指针的移动或偏移,其移动的步长是指针目标类型的长度。如:其步长为sizeof(char);其步长为sizeof(int);其步长为sizeof(double); 使用char指针类型,便于指针类型转换时各个指针类型长度偏移的计算,因为char的长度为一个字节。第1个宏 defineINTSIZEOF(n)((sizeof(n)sizeof(int)1)(sizeof(int)1)) 通过位运算实现栈按字长(int长度)对齐。includestdio。hintalignFloor(intn,intm){returnn(m1);位运算同等实现,2xm,x为正整数}intalignCelling(intn,intm)栈对齐要向上舍入{if0if(nm)return(nm)elsereturn(nm1)return(nm1)(m1);位运算同等实现,2xm,x为正整数endif}intmain(){intarr〔〕{1,2,3,4,5,6,7,8},m4;for(inti0;i8;i)printf(ddd,arr〔i〕,alignFloor(arr〔i〕,m),alignCelling(arr〔i〕,m));getchar();return0;}104204304444548648748888 2xm,x为正整数 m1如果用二进制表示,表示低位有x个1,其它高位都是0。 (m1)如果用二进制表示,表示低位有x个0,其它高位都是0。 当某个数与(m1)做位运算,x个低位都会置‘0’。 例如使用的是32位平台,sizeof(int)1)等于3,其二进制编码为: 0000000000000000000000000011 sizeof(int)1)等于4,其二进制编码为: 1111111111111111111111111100 当某一个数与sizeof(int)1)进行与运算()时,其最后两位如果是1会被置0,如果本身是0则不变,而低位的第3位的位置是4,对应sizeof(int)的值。第2个宏 definevastart(ap,v)(ap(valist)vINTSIZEOF(v)) char类型的指针ap指向变长参数列表前的参数v的下一个参数。 这里的v表示变长参数列表前的参数名,先取值,然后做类型转换,转换为char类型,其移动步长为1个字节,再加上v的字节数(宏考虑了栈字节对齐)。第3个宏 definevaarg(ap,t)((t)((apINTSIZEOF(t))INTSIZEOF(t))) 指针ap转换为t类型,并将ap偏移(向前移动)一个步长(sizeof(t),对转换后的ap做解引用。 ap加减同一个数并不是多此一举,虽然整体表达式计算的地址没有发生改变,但ap却产生了副作用(运算符),ap指向了下一个参数。 通常我们使用后置来下移一个数组元素:voidtest1(){doublearr〔〕{1,2,3,4,5,6};for(inti0;i)printf(。2f,p);后置可以实现指针后移一个元素} 但如果是用char指针指向double数组,无法使用后置操作,但可以变通一下:voidtest2(){doublearr〔〕{1,2,3,4,5,6};charq(char)for(intj0;j)printf(。2f,((double)((qsizeof(double))sizeof(double))));}第4个宏 definevaend(ap)(ap(valist)0) 将指针ap置NULL。 ref: https:www。douban。comnote707270642?i5521538AHiJZV,5534889AHiJZV https:www。toutiao。comi6857664589912343052 End