Chinaunix首页 | 论坛 | 博客
  • 博客访问: 475371
  • 博文数量: 133
  • 博客积分: 1235
  • 博客等级: 少尉
  • 技术积分: 1201
  • 用 户 组: 普通用户
  • 注册时间: 2010-09-08 19:59
文章分类

全部博文(133)

文章存档

2023年(12)

2022年(3)

2018年(2)

2017年(4)

2016年(4)

2015年(42)

2014年(1)

2013年(12)

2012年(16)

2011年(36)

2010年(1)

分类: 其他平台

2015-03-18 11:19:39

可变参数函数的原型声明格式为:

type VAFunction(type arg1, type arg2, … );

参数可以分为两部分:个数确定的固定参数和个数可变的可选参数。函数至少需要一个固定参数,固定参数的声明和普通函数一样;可选参数由于个数不确定,声明时用"…"表示。固定参数和可选参数公同构成一个函数的参数列表。

借助上面这个简单的例2,来看看各个va_xxx的作用。 
va_list arg_ptr:定义一个指向个数可变的参数列表指针;

va_start(arg_ptr, argN):使参数列表指针arg_ptr指向函数参数列表中的第一个可选参数, 说明:argN是位于第一个可选参数之前的固定参数,(或者说,最后一个固定参数;…之前的一个参数),函数参数列表中参数在内存中的顺序与函数声明时的顺序是一致的。如果有一va函数的声明是void va_test(char a, char b, char c, …),则它的固定参数依次是a,b,c,最后一个固定参数argN为c,因此就是va_start(arg_ptr, c)。

va_arg(arg_ptr, type):返回参数列表中指针arg_ptr所指的参数,返回类型为type,并使指针arg_ptr指向参数列表中下一个参数。

va_copy(dest, src):dest,src的类型都是va_list,va_copy()用于复制参数列表指针,将dest初始化为src。

va_end(arg_ptr):清空参数列表,并置参数指针arg_ptr无效。 说明:指针arg_ptr被置无效后,可以通过调用va_start()、va_copy()恢复arg_ptr。每次调用va_start() / va_copy()后,必须得有相应的va_end()与之匹配。参数指针可以在参数列表中随意地来回移动,但必须在va_start() … va_end()之内。
ANSI C标准下,va的宏定义在stdarg.h中,它们有:va_list,va_start(),va_arg(),va_end()。

简单地说,va函数的实现就是对参数指针的使用和控制。

typedef char *  va_list;  // x86平台下va_list的定义

函数的固定参数部分,可以直接从函数定义时的参数名获得;对于可选参数部分,先将指针指向第一个可选参数,然后依次后移指针,根据与结束标志的比较来判断是否已经获得全部参数。因此,va函数中结束标志必须事先约定好,否则,指针会指向无效的内存地址,导致出错。

这里,移动指针使其指向下一个参数,那么移动指针时的偏移量是多少呢,没有具体答案,因为这里涉及到内存对齐(alignment)问题,内存对齐跟具体使用的硬件平台有密切关系,比如大家熟知的32位x86平台规定所有的变量地址必须是4的倍数(sizeof(int) = 4)。va机制中用宏_INTSIZEOF(n)来解决这个问题,没有这些宏,va的可移植性无从谈起。

首先介绍宏_INTSIZEOF(n),它求出变量占用内存空间的大小,是va的实现的基础。

#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 )                           // 将指针置为无效

下表是针对函数int TestFunc(int n1, int n2, int n3, …) 参数传递时的内存堆栈情况。(C编译器默认的参数传递方式是__cdecl。)

对该函数的调用为int result = TestFunc(a, b, c, d. e); 其中e为结束标志。


在使用va-arg时,要注意第二个参数所用类型名应与传递到堆栈的参数的字节数对应,以保证能对不同类型的可变参数进行正确地寻址,比如实参依次为char型、char * 型、int型和float型时,在va-arg中它们的类型则应分别为int、char *、int和double.

void va-end(va-list ap)也是一个宏,该宏用于被调用函数完成正常返回,功能就是把指针ap赋值为0,使它不指向内存的变量。下面给出va_end在C中的源码:

#define va_end(ap)      ( ap = (va_list)0 ) 

va-end必须在va-arg读完所有参数后再调用,否则会产生意想不到的后果。特别地,当可变参数表函数在程序执行过程中不止一次被调用时,在函数体每次处理完可变参数表之后必须调用一次va-end,以保证正确地恢复栈。
    一个变参数函数至少需要有一个普通参数,其普通参数可以具有任何类型。在函数定义中,这种函数的最后一个普通参数除了一般的用途之外,还有其他特殊用途。下面从一个例子开始说明有关的问题。
假设我们想定义一个函数sum,它可以用任意多个整数类型的表达式作为参数进行调用,希望sum能求出这些参数的和。这时我们应该将sum定义为一个只有一个普通参数,并具有变长度参数表的函数,这个函数的头部应该是(函数原型与此类似):
int sum(int n, ...)
我们实际上要求在函数调用时,从第一个参数n得到被求和的表达式个数,从其余参数得到被求和的表达式。在参数表最后连续写三个圆点符号,说明这个函数具有可变数目的参数。凡参数表具有这种形式(最后写三个圆点),就表示定义的是一个变参数函数。注意,这样的三个圆点只能放在参数表最后,在所有普通参数之后。
下面假设函数sum里所用的va_list类型的变量的名字是vap。在能够用vap访问实际参数之前,必须首先用宏a_start对这个变量进行初始化。宏va_start的类型特征可以大致描述为:

va_start(va_list vap, 最后一个普通参数)
在函数sum里对vap初始化的语句应当写为:

va_start(vap, n); 相当于  char *vap= (char *)&n + sizeof(int);
此时vap正好指向n后面的可变参数表中的第一个参数。

在完成这个初始化之后,我们就可以通过另一个宏va_arg访问函数调用的各个实际参数了。宏va_arg的类型特征可以大致地描述为:
类型 va_arg(va_list vap, 类型名)
在调用宏va_arg时必须提供有关实参的实际类型,这一类型也将成为这个宏调用的返回值类型。对va_arg的调用不仅返回了一个实际参数的值(“当前”实际参数的值),同时还完成了某种更新操作,使对这个宏va_arg的下次调用能得到下一个实际参数。对于我们的例子,其中对宏va_arg的一次调用应当写为:
v = va_arg(vap, int);
这里假定v是一个有定义的int类型变量。
在变参数函数的定义里,函数退出之前必须做一次结束动作。这个动作通过对局部的va_list变量调用宏va_end完成。这个宏的类型特征大致是:
void va_end(va_list vap);


我们还需要注意一个陷阱,即va_arg宏的第2个参数不能被指定为char、short或者float类型。在可变参数函数传递时,因为char和short类型的参数会被提升为int类型,而float类型的参数会被提升为double类型 。

例如,以下的代码是错误的

a = va_arg(ap,char);

因为我们无法传递一个char类型参数,如果传递了,它将会被自动转化为int类型。上面的式子应该写成:

a = va_arg(ap,int);

变参函数代码示例

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

函数定义一个结束标记,调用时通过最后一个参数传递该标记,以结束变参的遍历打印
//最后一个参数作为变参结束符(-1),用于循环获取变参内容
void ParseVarArgByEnd(int dwStart, ...){
    va_list pArgs = NULL;
    va_start(pArgs, dwStart);
    int dwArgIdx = 0;
    int dwArgVal = dwStart;
    while(dwArgVal != -1){
        ++dwArgIdx;
        printf("The %dth Argument: %d\n",dwArgIdx, dwArgVal);
        dwArgVal = va_arg(pArgs, int); //得到下个变参值
    }
    va_end(pArgs);
} 函数自定义一些可能出现的参数类型,在变参列表中显式指定变参类型。可这样传递参数:参数数目,可变参数类型1,可变参数值1,可变参数类型2,可变参数值2,
//可变参数采用的形式传递,以处理不同的变参类型
typedef enum{
    CHAR_TYPE = 1,
    INT_TYPE,
    LONG_TYPE,
    FLOAT_TYPE,
    DOUBLE_TYPE,
    STR_TYPE
}E_VAR_TYPE;
void ParseVarArgType(int dwArgNum, ...){
    va_list pArgs = NULL;
    va_start(pArgs, dwArgNum);


    int i = 0;
    for(i = 0; i < dwArgNum; i++){
        E_VAR_TYPE eArgType = va_arg(pArgs, int);
        switch(eArgType){
            case INT_TYPE:
                printf("The %dth Argument: %d\n", i+1, va_arg(pArgs, int));
                break;
            case STR_TYPE:
                printf("The %dth Argument: %s\n", i+1, va_arg(pArgs, char*));
                break;
            default:
                break;
        }
    }
    va_end(pArgs);
} 实现简易的MyPrintf函数。该函数无返回值,即不记录输出的字符数目;接受"%d"按整数输出、"%c"按字符输出、"%b"按二进制输出,"%%"输出'%'本身
char *MyItoa(int iValue, char *pszResBuf, unsigned int uiRadix){
    //If pszResBuf is NULL, string "Nil" is returned.
    if(NULL == pszResBuf){
        //May add more trace/log output here
        return "Nil";
    }
    
    //If uiRadix(Base of Number) is out of range[2,36],
     //empty resulting string is returned.
    if((uiRadix < 2) || (uiRadix > 36)){
        //May add more trace/log output here
        *pszResBuf = '\0';
        return pszResBuf;
    }


    char *pStr = pszResBuf; //Pointer to traverse string
    char *pFirstDig = pszResBuf; //Pointer to first digit
    if((10 == uiRadix) && (iValue < 0)){ //Negative decimal number
        iValue = (unsigned int)-iValue;
        *pStr++ = '-';
        pFirstDig++;  //Skip negative sign
    }


    int iTmpValue = 0;
    do{
        iTmpValue = iValue;
        iValue /= uiRadix;
        //Calculating the modulus operator(%) by hand saving a division
        *pStr++ = "0123456789abcdefghijklmnopqrstuvwxyz"[iTmpValue - iValue * uiRadix];
    }while(iValue);
    *pStr-- = '\0';  //Terminate string, pStr points to last digit(or negative sign)
    //Now have a string of number in reverse order


    //Swap *pStr and *pFirstDig for reversing the string of number
    while(pFirstDig < pStr){ //Repeat until halfway
        char cTmpChar = *pStr;
        *pStr--= *pFirstDig;
        *pFirstDig++ = cTmpChar;
    }
    return pszResBuf;
}


void MyPrintf(const char *pszFmt, ... ){
    va_list pArgs = NULL;
    va_start(pArgs, pszFmt);


    for(; *pszFmt != '\0'; ++pszFmt){
        //若不是控制字符则原样输出字符
        if(*pszFmt != '%'){
            putchar(*pszFmt);
            continue;
        }


        //若是控制字符则查看下一字符
        switch(*++pszFmt){
            case '%': //连续两个'%'输出单个'%'
                putchar('%');
                break;
            case 'd': //按照整型输出
                printf("%d", va_arg(pArgs, int));
                break;
            case 'c': //按照字符输出
                printf("%c", va_arg(pArgs, int)); //不可写为...va_arg(pArgs, char);
                break;
            case 'b': {//按照二进制输出
                char aucStr[sizeof(int)*8 + 1] = {0};
                fputs(MyItoa(va_arg(pArgs, int), aucStr, 2), stdout);
                //printf(MyItoa(va_arg(pArgs, int), aucStr, 2));
                break;
            }
            default:
                vprintf(--pszFmt, pArgs);
                return;
        }
    }//end of for-loop
    va_end(pArgs);
}

	

问题1:可变长参数的获取

  有这样一个具有可变长参数的函数,其中有下列代码用来获取类型为float的实参:

  va_arg (argp, float);

  这样做可以吗?

  答案与分析:

  不可以。在可变长参数中,应用的是"加宽"原则。也就是float类型被扩展成double;char、 short类型被扩展成int。因此,如果你要去可变长参数列表中原来为float类型的参数,需要用va_arg(argp, double)。对char和short类型的则用va_arg(argp, int)。

  问题2:定义可变长参数的一个限制

  为什么我的编译器不允许我定义如下的函数,也就是可变长参数,但是没有任何的固定参数?

  1. int f(...) 
  2.     ...... 
  3.     ...... 
  4.     ...... 
答案与分析:   不可以。这是ANSI C 所要求的,你至少得定义一个固定参数。这个参数将被传递给va_start(),然后用va_arg()和va_end()来确定所有实际调用时可变长参数的类型和值。       问题3:如何判别可变参数函数的参数类型? 函数形式如下:
  1. void fun(char *str ,...) 
  2.     ...... 
  3.     ...... 
  4.     ...... 

若传的参数个数大于1,如何判别第2个以后传参的参数类型??? 答案与分析: 这个是没有办法判断的,例如printf( "%d%c%s ",  ....)是通过格式串中的%d、 %c、 %s来确定后面参数的类型,其实你也可以参考这种方法来判断不定参数的类型。


阅读(959) | 评论(0) | 转发(0) |
0

上一篇:Makefile笔记

下一篇:netstat的几个基本用法

给主人留下些什么吧!~~