当前位置:   article > 正文

可变参数函数详解_*pstr++

*pstr++

   可变参数函数又称参数个数可变函数(本文也简称变参函数),即函数参数数目可变。原型声明格式为:

type VarArgFunc(type FixedArg1, type FixedArg2, …);

     其中,参数可分为两部分:数目确定的固定参数和数目可变的可选参数。函数至少需要一个固定参数,其声明与普通函数参数相同;可选参数由于数目不定(0个或以上),声明时用"…"表示(“…”用作参数占位符)。固定参数和可选参数共同构成可变参数函数的参数列表。

     由于参数数目不定,使用可变参数函数通常能缩短编码,灵活性和易用性较高。

     典型的变参函数如printf(及其家族),其函数原型为:

int printf(const char* format, ...);

     printf函数除参数format固定外,后续参数的数目和类型均可变。实际调用时可有以下形式:

printf("string"); 

		<p>printf("%d", i);&nbsp;</p>

		<p>printf("%s", s);&nbsp;</p>

		<p>printf("number&nbsp;is&nbsp;%d, string&nbsp;is:%s",&nbsp;i,&nbsp;s);</p>

		<p>……</p>
		</td>
	</tr></tbody></table></div><h3><a name="t0"></a><a name="t0"></a>&nbsp;</h3>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

1 变参函数实现原理

    C调用约定下可使用va_list系列变参宏实现变参函数,此处va意为variable-argument(可变参数)。典型用法如下:

#include <stdarg.h>

		<p>int VarArgFunc(int dwFixedArg, ...){ //以固定参数的地址为起点依次确定各变参的内存起始地址</p>

		<p>&nbsp;&nbsp;&nbsp;&nbsp;<strong>va_list</strong>&nbsp;pArgs = NULL;&nbsp; //定义va_list类型的指针pArgs,用于存储参数地址</p>

		<p><strong>&nbsp; &nbsp; va_start</strong>(pArgs, dwFixedArg); //初始化pArgs指针,使其指向第一个可变参数。该宏第二个参数是变参列表的前一个参数,即最后一个固定参数</p>

		<p>&nbsp; &nbsp; int dwVarArg =<strong>&nbsp;va_arg</strong>(pArgs, int); //该宏返回变参列表中的当前变参值并使pArgs指向列表中的下个变参。该宏第二个参数是要返回的当前变参类型</p>

		<p>&nbsp; &nbsp; //若函数有多个可变参数,则依次调用va_arg宏获取各个变参</p>

		<p><strong>&nbsp; &nbsp; va_end</strong>(pArgs);&nbsp; //将指针pArgs置为无效,结束变参的获取</p>

		<p><em>&nbsp; &nbsp; /* Code Block using variable arguments */</em></p>

		<p>}</p>

		<p>//可在头文件中声明函数为extern int VarArgFunc(int dwFixedArg, ...);,调用时用VarArgFunc(FixedArg, VarArg);</p>
		</td>
	</tr></tbody></table></div><p>&nbsp; &nbsp; &nbsp;变参宏根据堆栈生长方向和参数入栈特点,从最靠近第一个可变参数的固定参数开始,依次获取每个可变参数的地址。</p>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

     变参宏的定义和实现因操作系统、硬件平台及编译器而异(但原理相似)。System V Unix在varargs.h头文件中定义va_start宏为va_start(va_list arg_ptr),而ANSI C则在stdarg.h头文件中定义va_start宏为va_start(va_list arg_ptr, prev_param)。两种宏并不兼容,为便于程序移植通常采用ANSI C定义。

     gcc编译器使用内置宏间接实现变参宏,如#define va_start(v,l)  __builtin_va_start(v,l)。因为gcc编译器需要考虑跨平台处理,而其实现因平台而异。例如x86-64或PowerPC处理器下,参数不全都通过堆栈传递,变参宏的实现相比x86处理器更为复杂。

     x86平台VC6.0编译器中,stdarg.h头文件内变参宏定义如下:

typedef char * va_list;

		<p>#define _INTSIZEOF(n) &nbsp; &nbsp; &nbsp; ( (sizeof(n)+sizeof(int)-1) &amp; ~(sizeof(int)-1) )</p>

		<p>#define va_start(ap,v) &nbsp; &nbsp; &nbsp; &nbsp;( ap = (va_list)&amp;v + _INTSIZEOF(v) )</p>

		<p>#define va_arg(ap, type)&nbsp;&nbsp; &nbsp;( *(type *)((ap += _INTSIZEOF(type)) - _INTSIZEOF(type)) )</p>

		<p>#define va_end(ap) &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ( ap = (va_list)0 )</p>
		</td>
	</tr></tbody></table></div><p>&nbsp; &nbsp; &nbsp;各宏的含义如下:</p>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

     ①_INTSIZEOF宏考虑到某些系统需要内存地址对齐。从宏名看应按照sizeof(int)即堆栈粒度对齐,即参数在内存中的地址均为sizeof(int)=4的倍数。例如,若在1≤sizeof(n)≤4,则_INTSIZEOF(n)=4;若5≤sizeof(n)≤8,则_INTSIZEOF(n)=8。

     为便于理解,简化该宏为

#define _INTSIZEOF(n)  ((sizeof(n) + x) & ~(x))

		<p>x = sizeof(int) - 1 = 3 = 0b’0000 0000 0000 0011</p>

		<p>~x = 0b’1111 1111 1111 1100</p>
		</td>
	</tr></tbody></table></div><p>&nbsp; &nbsp; &nbsp;一个数与(~x)相与的结果是sizeof(int)的倍数,即_INTSIZEOF(n)将n圆整为sizeof(int)的倍数。</p>
  • 1
  • 2
  • 3
  • 4
  • 5

     ②va_start宏根据(va_list)&v得到第一个可变参数前的一个固定参数在堆栈中的内存地址,加上_INTSIZEOF(v)即v所占内存大小后,使ap指向固定参数后下个参数(第一个可变参数地址)。

     固定参数的地址用于va_start宏,因此不能声明为寄存器变量(地址无效)或作为数组类型(长度难定)。

     ③va_arg宏取得type类型的可变参数值。首先ap+=_INTSIZEOF(type),即ap跳过当前可变参数而指向下个变参的地址;然后ap-_INTSIZEOF(type)得到当前变参的内存地址,类型转换后返回当前变参值。

     va_arg宏的等效实现如下

//将指针移动至下个变参,并返回左移的值[-1](数组下标表示偏移量),即当前变参值

		<p>#define va_arg(ap,type)&nbsp; ((type *)((ap) += _INTSIZEOF(type)))[-1]</p>
		</td>
	</tr></tbody></table></div><p>&nbsp; &nbsp; &nbsp;④va_end宏使ap不再指向有效的内存地址。该宏的某些实现定义为((void*)0),编译时不会为其产生代码,调用与否并无区别。但某些实现中va_end宏用于函数返回前完成一些必要的清理工作:如va_start宏可能以某种方式修改堆栈,导致返回操作无法完成,va_end宏可将有关修改复原;又如va_start宏可能对参数列表动态分配内存以便于遍历va_list,va_end宏可释放此前动态分配的内存。因此,从使用va_start宏的函数中退出之前,必须调用一次va_end宏。</p>
  • 1
  • 2
  • 3

     函数内可多次遍历可变参数,但每次必须以va_start宏开始,因为遍历后ap指针不再指向首个变参。

     下图给出基于变参宏的可变参数在堆栈中的分布:

     变参宏无法智能识别可变参数的数目和类型,因此实现变参函数时需自行判断可变参数的数目和类型。前者可显式提供变参数目或设定遍历结束条件(如-1、'\0'或回车符等)。后者可显式提供变参类型枚举值,或在固定参数中包含足够的类型信息(如printf函数通过分析format字符串即可确定各变参类型),甚至主调函数和被调函数可约定变参的类型组织等。

 

2 变参函数代码示例

     本节给出若干遵循ANSI C标准形式的简单可变参数函数,基于这些示例可构造更为复杂实用的功能。

     示例函数必须包含stdio.h和stdarg.h头文件,并按需包含string.h头文件。

    【示例1】函数接受一个整型固定参数和一个整型可变参数,并打印这两个参数值。


 
 
  1. 1 void IntegerVarArgFunc(int i, ...){
  2. 2 va_list pArgs = NULL;
  3. 3 va_start(pArgs, i);
  4. 4 int j = va_arg(pArgs, int);
  5. 5 va_end(pArgs);
  6. 6 printf( "i=%d, j=%d\n", i, j);
  7. 7 }
  • 1

     分别采用以下三种方法调用:

     1) IntegerVarArgFunc(10);

     输出i=10, j=6803972(形参i的堆栈上方内容)

     2) IntegerVarArgFunc(10, 20);

     输出i=10, j=20,符合期望。

     3) IntegerVarArgFunc(10, 20, 30);

     输出i=10, j=20,多余的变参被忽略。

 

    【示例2】函数通过固定参数指定可变参数个数,循环打印所有变参值。


 
 
  1. 1 //第一个参数定义可变参数个数,用于循环获取变参内容
  2. 2 void ParseVarArgByNum(int dwArgNum, ...){
  3. 3 va_list pArgs = NULL;
  4. 4 va_start(pArgs, dwArgNum);
  5. 5 int dwArgIdx;
  6. 6 int dwArgVal = 0;
  7. 7 for(dwArgIdx = 1; dwArgIdx <= dwArgNum; dwArgIdx++){
  8. 8 dwArgVal = va_arg(pArgs, int);
  9. 9 printf( "The %dth Argument: %d\n",dwArgIdx, dwArgVal);
  10. 10 }
  11. 11 va_end(pArgs);
  12. 12 }
  • 1

     调用方式为ParseVarArgByNum(3, 11, 22, 33);,输出:

     The 1th Argument: 11

     The 2th Argument: 22

     The 3th Argument: 33

 

    【示例3】函数定义一个结束标记,调用时通过最后一个参数传递该标记,以结束变参的遍历打印。


 
 
  1. 1 //最后一个参数作为变参结束符(-1),用于循环获取变参内容
  2. 2 void ParseVarArgByEnd(int dwStart, ...){
  3. 3 va_list pArgs = NULL;
  4. 4 va_start(pArgs, dwStart);
  5. 5 int dwArgIdx = 0;
  6. 6 int dwArgVal = dwStart;
  7. 7 while(dwArgVal != -1){
  8. 8 ++dwArgIdx;
  9. 9 printf( "The %dth Argument: %d\n",dwArgIdx, dwArgVal);
  10. 10 dwArgVal = va_arg(pArgs, int); //得到下个变参值
  11. 11 }
  12. 12 va_end(pArgs);
  13. 13 }
  • 1

     调用方式为ParseVarArgByEnd(44, 55, -1);,输出:

     The 1th Argument: 44

     The 2th Argument: 55

 

    【示例4】函数自定义一些可能出现的参数类型,在变参列表中显式指定变参类型。可这样传递参数:参数数目,可变参数类型1,可变参数值1,可变参数类型2,可变参数值2,....。


 
 
  1. 1 //可变参数采用<ArgType, ArgValue>的形式传递,以处理不同的变参类型
  2. 2 typedef enum{
  3. 3 CHAR_TYPE = 1,
  4. 4 INT_TYPE,
  5. 5 LONG_TYPE,
  6. 6 FLOAT_TYPE,
  7. 7 DOUBLE_TYPE,
  8. 8 STR_TYPE
  9. 9 }E_VAR_TYPE;
  10. 10 void ParseVarArgType(int dwArgNum, ...){
  11. 11 va_list pArgs = NULL;
  12. 12 va_start(pArgs, dwArgNum);
  13. 13
  14. 14 int i = 0;
  15. 15 for(i = 0; i < dwArgNum; i++){
  16. 16 E_VAR_TYPE eArgType = va_arg(pArgs, int);
  17. 17 switch(eArgType){
  18. 18 case INT_TYPE:
  19. 19 printf( "The %dth Argument: %d\n", i+ 1, va_arg(pArgs, int));
  20. 20 break;
  21. 21 case STR_TYPE:
  22. 22 printf( "The %dth Argument: %s\n", i+ 1, va_arg(pArgs, char*));
  23. 23 break;
  24. 24 default:
  25. 25 break;
  26. 26 }
  27. 27 }
  28. 28 va_end(pArgs);
  29. 29 }
  • 1

     调用方式为ParseVarArgType(2, INT_TYPE, 222, STR_TYPE, "HelloWorld!");,输出:

     The 1th Argument: 222

     The 2th Argument: HelloWorld!

 

    【示例5】实现简易的MyPrintf函数。该函数无返回值,即不记录输出的字符数目;接受"%d"按整数输出、"%c"按字符输出、"%b"按二进制输出,"%%"输出'%'本身。


 
 
  1. 1 char *MyItoa(int iValue, char *pszResBuf, unsigned int uiRadix){
  2. 2 //If pszResBuf is NULL, string "Nil" is returned.
  3. 3 if( NULL == pszResBuf){
  4. 4 //May add more trace/log output here
  5. 5 return "Nil";
  6. 6 }
  7. 7
  8. 8 //If uiRadix(Base of Number) is out of range[2,36],
  9. 9 //empty resulting string is returned.
  10. 10 if((uiRadix < 2) || (uiRadix > 36)){
  11. 11 //May add more trace/log output here
  12. 12 *pszResBuf = '\0';
  13. 13 return pszResBuf;
  14. 14 }
  15. 15
  16. 16 char *pStr = pszResBuf; //Pointer to traverse string
  17. 17 char *pFirstDig = pszResBuf; //Pointer to first digit
  18. 18 if(( 10 == uiRadix) && (iValue < 0)){ //Negative decimal number
  19. 19 iValue = ( unsigned int)-iValue;
  20. 20 *pStr++ = '-';
  21. 21 pFirstDig++; //Skip negative sign
  22. 22 }
  23. 23
  24. 24 int iTmpValue = 0;
  25. 25 do{
  26. 26 iTmpValue = iValue;
  27. 27 iValue /= uiRadix;
  28. 28 //Calculating the modulus operator(%) by hand saving a division
  29. 29 *pStr++ = "0123456789abcdefghijklmnopqrstuvwxyz"[iTmpValue - iValue * uiRadix];
  30. 30 } while(iValue);
  31. 31 *pStr-- = '\0'; //Terminate string, pStr points to last digit(or negative sign)
  32. 32 //Now have a string of number in reverse order
  33. 33
  34. 34 //Swap *pStr and *pFirstDig for reversing the string of number
  35. 35 while(pFirstDig < pStr){ //Repeat until halfway
  36. 36 char cTmpChar = *pStr;
  37. 37 *pStr--= *pFirstDig;
  38. 38 *pFirstDig++ = cTmpChar;
  39. 39 }
  40. 40 return pszResBuf;
  41. 41 }
  42. 42
  43. 43 void MyPrintf(const char *pszFmt, ... ){
  44. 44 va_list pArgs = NULL;
  45. 45 va_start(pArgs, pszFmt);
  46. 46
  47. 47 for(; *pszFmt != '\0'; ++pszFmt){
  48. 48 //若不是控制字符则原样输出字符
  49. 49 if(*pszFmt != '%'){
  50. 50 putchar(*pszFmt);
  51. 51 continue;
  52. 52 }
  53. 53
  54. 54 //若是控制字符则查看下一字符
  55. 55 switch(*++pszFmt){
  56. 56 case '%': //连续两个'%'输出单个'%'
  57. 57 putchar( '%');
  58. 58 break;
  59. 59 case 'd': //按照整型输出
  60. 60 printf( "%d", va_arg(pArgs, int));
  61. 61 break;
  62. 62 case 'c': //按照字符输出
  63. 63 printf( "%c", va_arg(pArgs, int)); //不可写为...va_arg(pArgs, char);
  64. 64 break;
  65. 65 case 'b': { //按照二进制输出
  66. 66 char aucStr[ sizeof( int)* 8 + 1] = { 0};
  67. 67 fputs(MyItoa(va_arg(pArgs, int), aucStr, 2), stdout);
  68. 68 //printf(MyItoa(va_arg(pArgs, int), aucStr, 2));
  69. 69 break;
  70. 70 }
  71. 71 default:
  72. 72 vprintf(--pszFmt, pArgs);
  73. 73 return;
  74. 74 }
  75. 75 } //end of for-loop
  76. 76 va_end(pArgs);
  77. 77 }
  • 1

     调用方式为MyPrintf("Binary string of number %d is = %b!\n", 9999, 9999);,输出:

     Binary string of number 9999 is = 10011100001111!

     注意,MyPrintf函数for循环语句段旨在自定义格式化输出(如%b),而非实现printf库函数本身;否则直接使用vprintf(pszFmt, pArgs);即可。此外该函数存在一处明显缺陷,即%b前若出现case匹配项外的控制字符(如%x),则会调用vprintf函数处理该字符及其后的格式串,%b将会原样输出"%b"(而非转换为二进制)。

     本示例中也附带实现了MyItoa函数。该函数与非标准C语言扩展函数itoa功能相同。该函数将整数iValue转换为uiRadix 所指定的进制数字符串,并将其存入pszResBuf字符数组。

 

    【示例6】可变参数数目不多时,可用数组或结构体数组变相实现可变参数函数。


 
 
  1. #define VAR_ARG_MAX_NUM (unsigned char)10
  2. #define VAR_ARG_MAX_LEN (unsigned char)20
  3. //可变参数信息
  4. typedef struct{
  5. E_VAR_TYPE eArgType;
  6. unsigned char aucArgVal[VAR_ARG_MAX_LEN];
  7. }VAR_ARG_ENTRY;
  8. typedef struct{
  9. unsigned char ucArgNum;
  10. VAR_ARG_ENTRY aucVarArg[VAR_ARG_MAX_NUM];
  11. }VAR_ARG_LIST;
  12. void ParseStructArrayArg(VAR_ARG_LIST *ptVarArgList){
  13. int i = 0;
  14. for(i = 0; i < ptVarArgList->ucArgNum; i++){
  15. E_VAR_TYPE eArgType = ptVarArgList->aucVarArg[i].eArgType;
  16. switch(eArgType){
  17. case CHAR_TYPE:
  18. printf( "The %dth Argument: %c\n", i+ 1, ptVarArgList->aucVarArg[i].aucArgVal[ 0]);
  19. break;
  20. case STR_TYPE:
  21. printf( "The %dth Argument: %s\n", i+ 1, ptVarArgList->aucVarArg[i].aucArgVal);
  22. break;
  23. default:
  24. break;
  25. }
  26. }
  27. }
  • 1

     调用方式为

VAR_ARG_LIST tVarArgList = {2, {{CHAR_TYPE, {'H'}}, {STR_TYPE, "TEST"}}};

		<p>ParseStructArrayArg(&amp;tVarArgList);</p>
		</td>
	</tr></tbody></table></div><p>&nbsp; &nbsp; &nbsp;输出:</p>
  • 1
  • 2
  • 3

     The 1th Argument: H

     The 2th Argument: TEST

     本示例函数原型稍加改造,显式声明参数数目如下:

void ParseStructArrayArg(unsigned char ucArgNum, VAR_ARG_ENTRY aucVarArg[]);或

		<p>void ParseStructArrayArg(unsigned char ucArgNum, VAR_ARG_ENTRY *aucVarArg);</p>
		</td>
	</tr></tbody></table></div><p>&nbsp; &nbsp; &nbsp;改造后的原型与main函数的带参原型非常相似!</p>
  • 1
  • 2
  • 3

int main(int argc, char *argv[]);或

		<p>int main(int argc, char **argv);</p>
		</td>
	</tr></tbody></table></div><p>&nbsp; &nbsp; &nbsp;若VAR_ARG_ENTRY内的变参数目和类型固定,则主调函数和被调函数双方约定后可采用char型数组替代VAR_ARG_ENTRY结构体数组。</p>
  • 1
  • 2
  • 3

     通过数组可替代某些不必要的变参函数实现,如对整数求和:

实现方式

可变参数函数

数组替代

函数代码

int SumVarArg(int dwStart, ...){

		<p>&nbsp;&nbsp;&nbsp; va_list pArgs = NULL;</p>

		<p>&nbsp;&nbsp;&nbsp; va_start(pArgs, dwStart);</p>

		<p>&nbsp;&nbsp;&nbsp; int dwArgVal = dwStart, dwSum = 0;</p>

		<p>&nbsp;&nbsp;&nbsp; while(dwArgVal != 0){ //0为结束标志</p>

		<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; dwSum += dwArgVal;</p>

		<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; dwArgVal = va_arg(pArgs, int);</p>

		<p>&nbsp;&nbsp;&nbsp; };</p>

		<p>&nbsp;&nbsp;&nbsp; va_end(pArgs);</p>

		<p>&nbsp;&nbsp;&nbsp; return dwSum;</p>

		<p>}</p>
		</td>
		<td style="vertical-align:top;">
		<p>int SumArray(int aucArr[], int dwSize){</p>

		<p>&nbsp;&nbsp;&nbsp; int i = 0, dwSum = 0;</p>

		<p>&nbsp;&nbsp;&nbsp; for(i = 0; i &lt; dwSize; i++){</p>

		<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; dwSum += aucArr[i];</p>

		<p>&nbsp;&nbsp;&nbsp; }</p>

		<p>&nbsp;&nbsp;&nbsp; return dwSum;</p>

		<p>}</p>
		</td>
	</tr><tr><td>
		<p><strong>调用方式</strong></p>
		</td>
		<td style="vertical-align:top;">
		<p>SumVarArg(7, 2, 7, 11, -2, 0);</p>
		</td>
		<td style="vertical-align:top;">
		<p>int aucArr[] = {7, 2, 7, 11, -2};</p>

		<p>SumArrayArg(aucArr, sizeof(aucArr)/sizeof(aucArr[0]));</p>
		</td>
	</tr></tbody></table></div><p>&nbsp; &nbsp; &nbsp;数组方式调用时可方便地指定求和项的起止,如SumArrayArg(&amp;aucArr[1], 3)将从数组aucArr的第2个元素开始累加3个元素,即2+7+11=20。而这是变参函数SumVarArg无法做到的。</p>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47

 

3 变参函数注意事项

     可变参数函数在编程中应注意以下问题:

     1) 编译器对可变参数函数的原型检查不够严格,不利于编程查错。

     调用变参函数时,传递的变参数目应不少于该函数所期望的变参数目(该数目由主调函数实参指定或由变参函数内部实现决定),否则会访问到函数参数以外的堆栈区域,可能导致堆栈错误。

     如示例1中可变参数为char*类型(用%s打印) 时,若使用整型变参调用该函数,可能会出现段错误(Linux)或页面非法错误(Windows),也可能出现难以觉察的细微错误。

     printf函数格式化字符串参数所指定的类型与后面变参的类型不匹配时,也可能造成程序崩溃(尤其以%s打印整型参数值时)。

     gcc编译器提供attribute 机制用以编译时检查某些变参函数调用情况,如声明函数为

void OmciLog(LOG_TYPE eLogType, const char *pFmt, ...) __attribute__((format(printf,2,3)));

     表示函数原型中第2个参数(pFmt)为格式化字符串,从参数列表中第3个参数(即首个变参)开始与pFmt形式比较。该声明将对OmciLog(LOG_PON, "%s", 1)的调用产生编译警告:

VarArgs.c:204: warning: format '%s' expects type 'char *', but argument 3 has type 'int'

     但该机制主要针对类似scanf/printf的变参函数,此类函数可根据格式化字符串确定变参数目和类型。

     2) va_arg(ap, type)宏获取变参时,type不可指定为以下类型:

  • char、signed char、unsigned char
  • short、unsigned short
  • signed shortshort int、signed short int、unsigned short int
  • float

     在C语言中,调用不带原型声明或声明为变参的函数时,主调函数会在传递未显式声明的参数前对其执行“缺省参数提升(default argument promotions)”,将提升后的参数值传递给被调函数。

     提升操作如下:

  • float类型的参数提升为double类型
  • char、short和相应的signed、unsigned类型参数提升为int类型
  • 若int类型不能存储原值,则提升为unsigned int类型

     在gcc 编译器中,若type使用char或unsigned short int等需提升的类型,可能会得到严重警告。 

     因此,若要获取变参数列表中float类型的实参,则变参函数中应使用double dVar = va_arg(ap, double)或float fVar = (float)va_arg(ap, double)。char和short类型实参处理方式与之类似。

     3) 使用va_arg宏获取变参列表中类型为函数指针的参数时,可能需要将函数指针用typedef定义为新的数据类型,以便通过编译(与va_arg宏的实现有关)。

     对于VC6.0的va_arg宏实现,若用该宏从变参列表中提取函数指针类型的参数,如

va_arg(argp, int(*)());

     被扩展为以下形式(为缩减长度直接写出_INTSIZEOF宏值)

( *(int (*)() *)((pArgs += 4) - 4) );

     显然,(int (*)() *)无意义。

     解决方法如下

typedef int (*pFunc)();

     va_arg(argp, pFunc)被扩展为(*(pFunc *)((pArgs += 4) - 4)),即可通过编译检查。

     而在gcc编译器下,va_arg宏可直接使用函数指针类型。


 
 
  1. 1 //for Gcc Compiler
  2. 2 int DummyFunc(void){ printf( "Here!!!\n"); return 0; }
  3. 3 void ParseFuncPtrVarArg(int i, ...){
  4. 4 va_list pArgs = NULL;
  5. 5 va_start(pArgs, i);
  6. 6 char *sVal = va_arg(pArgs, char*);
  7. 7 va_end(pArgs);
  8. 8 printf( "%d %s ", i, sVal);
  9. 9
  10. 10 int (*pf)() = va_arg(pArgs, int (*)());
  11. 11 pf();
  12. 12 }
  • 1

     以ParseFuncPtrVarArg(1, "Welcome", DummyFunc);方式调用,输出为1 Welcome Here!!!。

     4) C语言层面上无法将函数A的可变参数直接传递给函数B。只能定义被调函数的参数为va_list类型,在主调函数中将可变参数列表转换为va_list,再进行可变参数的传递。这种技巧常用于定制打印函数:


 
 
  1. 1 INT32S OmciLog(E_LOG_TYPE eLogType, const CHAR *pszFmt, ...){
  2. 2 CHECK_SINGLE_POINTER(pFormat, RETURN_VOID);
  3. 3
  4. 4 if( 0 == GET_BIT(gOmciLogCtrl, eLogType))
  5. 5 return;
  6. 6
  7. 7 CHAR aucLogBuf[OMCI_LOG_BUF_LEN] = { 0};
  8. 8 va_list pArgs = NULL;
  9. 9 va_start(pArgs, pszFmt);
  10. 10 INT32S dwRetVal = vsnprintf(aucLogBuf, sizeof(aucLogBuf), pszFmt, pArgs);
  11. 11 va_end(pArgs);
  12. 12
  13. 13 OUTPUT_LOG(aucLogBuf);
  14. 14 return dwRetVal;
  15. 15 }
  • 1

     其中被调函数vsnprintf可根据va_arg(pszFmt, pArgs)依次取出所需的变参。

     以OmciLog("%d %f %s\n", 10, 20.3, "ABC");方式调用,输出为10 20.300000 ABC。

     5) 可变参数必须从头到尾按照顺序逐个访问。可访问几个变参后中止,但不能一开始就访问变参列表中间的参数。

     6) ANSI C要求至少定义一个固定参数(ISO C requires a named argument before '...'),该参数将传递给va_start宏以查找参数列表的可变部分。故不可定义void func(...)这样的函数。

     7) 变参宏实现与堆栈相关,在参数入寄存器的处理器下实现可能异常复杂(gcc中va_start宏会将所有可能用于变参传递的寄存器均保存在栈中)。因此如非必要,应尽量避免使用变参宏。C语言中除示例6中数组或结构体数组替代方式外,还可采用回调函数方式"抛出"变化部分,如:


 
 
  1. 1 /**********************************************************************
  2. 2 * 函数名称: OmciLocateListNode
  3. 3 * 功能描述: 查找链表首个与pData满足函数fCompareNode判定关系的结点
  4. 4 * 输入参数: T_OMCI_LIST* pList :链表指针
  5. 5 * VOID* pData :待比较数据指针
  6. 6 * CompareNodeFunc fCompareNode :比较回调函数指针
  7. 7 * 输出参数: NA
  8. 8 * 返 回 值: T_OMCI_LIST_NODE* 链表结点指针(未找到时返回NULL)
  9. 9 ***********************************************************************/
  10. 10 T_OMCI_LIST_NODE* OmciLocateListNode(T_OMCI_LIST *pList, VOID *pData, CompareNodeFunc fCompareNode)
  11. 11 {
  12. 12 CHECK_TRIPLE_POINTER(pList, pData, fCompareNode, NULL);
  13. 13 CHECK_SINGLE_POINTER(pList->pHead, NULL);
  14. 14 CHECK_SINGLE_POINTER(pList->pHead->pNext, NULL);
  15. 15
  16. 16 if( 0 == pList->dwNodeNum)
  17. 17 {
  18. 18 return NULL;
  19. 19 }
  20. 20
  21. 21 T_OMCI_LIST_NODE *pListNode = pList->pHead->pNext;
  22. 22 while(pListNode != pList->pHead)
  23. 23 {
  24. 24 if( 0 == fCompareNode(pListNode->pNodeData, pData, pList->dwNodeDataSize))
  25. 25 return pListNode;
  26. 26
  27. 27 pListNode = pListNode->pNext;
  28. 28 }
  29. 29
  30. 30 return NULL;
  31. 31 }
  • 1

     OmciLocateListNode函数是下面Omci_List_Query函数的另一实现。主调函数提供fCompareNode回调函数以比较链表结点,从而简化代码实现,并增强可读性。


 
 
  1. 1 /***************************************************************
  2. 2 * Function: Omci_List_Query
  3. 3 * Description -
  4. 4 * 根据给定的KEY偏移和KEY长度,查找目标节点
  5. 5 * Input:
  6. 6 * pList: 链表
  7. 7 * 可变参数: 三个参数为一组,第一个为key value,第二个为key
  8. 8 * 偏移,第三个为key长度,以LIST_END表示参数结束。
  9. 9 * Output:
  10. 10 * Returns:
  11. 11 *
  12. 12 * modification history
  13. 13 * -------------------------------
  14. 14 * Created : 2011-5-25 by xxx
  15. 15 * ------------------------------
  16. 16 ***************************************************************/
  17. 17 OMCI_LIST_NODE* Omci_List_Query(OMCI_LIST *pList, ...)
  18. 18 {
  19. 19 OMCI_LIST_NODE_KEY aKeyGroup[MAX_LIST_NODE_KEYS_NUM];
  20. 20 OMCI_LIST_NODE *pNode= NULL;
  21. 21 INT8U *pData= NULL, *pKeyValue= NULL;
  22. 22 INT8U ucKeyNum= 0, i;
  23. 23 INT32U iKeyOffset= 0, iKeyLen= 0;
  24. 24 VA_LIST tArgList;
  25. 25
  26. 26 if( NULL==pList)
  27. 27 return NULL;
  28. 28 memset((INT8U*)aKeyGroup, 0, sizeof(OMCI_LIST_NODE_KEY)*MAX_LIST_NODE_KEYS_NUM);
  29. 29 VA_START(tArgList, pList);
  30. 30 while(TRUE)
  31. 31 {
  32. 32 pKeyValue=VA_ARG(tArgList, INT8U*);
  33. 33 if(LIST_END==pKeyValue)
  34. 34 break;
  35. 35 iKeyOffset=VA_ARG(tArgList, INT32U);
  36. 36 iKeyLen=VA_ARG(tArgList, INT32U);
  37. 37 if( 0==iKeyLen)
  38. 38 {
  39. 39 VA_END(tArgList);
  40. 40 return NULL;
  41. 41 }
  42. 42 if(ucKeyNum>=MAX_LIST_NODE_KEYS_NUM)
  43. 43 {
  44. 44 VA_END(tArgList);
  45. 45 return NULL;
  46. 46 }
  47. 47 aKeyGroup[ucKeyNum].pKeyValue=pKeyValue;
  48. 48 aKeyGroup[ucKeyNum].iKeyOffset=iKeyOffset;
  49. 49 aKeyGroup[ucKeyNum++].iKeyLen=iKeyLen;
  50. 50 }
  51. 51 VA_END(tArgList);
  52. 52
  53. 53 pNode=Omci_List_First(pList);
  54. 54 while( NULL!=pNode)
  55. 55 {
  56. 56 pData=(INT8U*)pNode->pNodeData;
  57. 57 for(i= 0; i<ucKeyNum; i++)
  58. 58 {
  59. 59 if( 0!= memcmp(&pData[aKeyGroup[i].iKeyOffset], aKeyGroup[i].pKeyValue, aKeyGroup[i].iKeyLen))
  60. 60 break;
  61. 61 }
  62. 62 if(i>=ucKeyNum)
  63. 63 {
  64. 64 break;
  65. 65 }
  66. 66 pNode=pNode->pNext;
  67. 67 }
  68. 68 return pNode;
  69. 69 }
  • 1

     在C++语言里,可利用多态性来实现可变参数的功能(但灵活性有所下降)。 

 

【扩展阅读】vsnprintf函数

		<p>vsnprintf函数原型为:int vsnprintf(char *str, size_t size, const char *format, va_list ap)。</p>

		<p>该函数将根据format字符串来转换并格式化ap所指向的可变参数列表,并将结果字符串以不超过size字节(包括字符串结束符'\0')的长度写入str所指向的字符串缓冲区(该缓冲区大小至少为size字节)。若结果字符串超过size-1个字符,则丢弃多余字节,但将其计入函数返回值。若函数执行成功,则返回实际或本该写入的字符数目(包括字符串结束符);否则将返回负值。因此,仅当返回值为小于size的非负值时,表明结果字符串被完全写入(大于等于size则意味着字符串被截断)。snprintf函数的返回值规则与之相同。</p>

		<p>注意,当目的缓冲区不够大时会截断字符串,但vsnprintf/snprintf函数确保缓冲区中存放的字符串以NULL结尾,而stncpy函数处理后的字符串不含结束符。</p>
		</td>
	</tr></tbody></table></div><p>&nbsp;</p>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

 

声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号