• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

            eryar

            PipeCAD - Plant Piping Design Software.
            RvmTranslator - Translate AVEVA RVM to OBJ, glTF, etc.
            posts - 603, comments - 590, trackbacks - 0, articles - 0

            可變參數(shù)函數(shù)詳解

             

                 可變參數(shù)函數(shù)又稱參數(shù)個(gè)數(shù)可變函數(shù)(本文也簡(jiǎn)稱變參函數(shù)),即函數(shù)參數(shù)數(shù)目可變。原型聲明格式為:

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

                 其中,參數(shù)可分為兩部分:數(shù)目確定的固定參數(shù)和數(shù)目可變的可選參數(shù)。函數(shù)至少需要一個(gè)固定參數(shù),其聲明與普通函數(shù)參數(shù)相同;可選參數(shù)由于數(shù)目不定(0個(gè)或以上),聲明時(shí)用"…"表示(“…”用作參數(shù)占位符)。固定參數(shù)和可選參數(shù)共同構(gòu)成可變參數(shù)函數(shù)的參數(shù)列表。

                 由于參數(shù)數(shù)目不定,使用可變參數(shù)函數(shù)通常能縮短編碼,靈活性和易用性較高。

                 典型的變參函數(shù)如printf(及其家族),其函數(shù)原型為:

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

                 printf函數(shù)除參數(shù)format固定外,后續(xù)參數(shù)的數(shù)目和類型均可變。實(shí)際調(diào)用時(shí)可有以下形式:

            printf("string"); 

            printf("%d", i); 

            printf("%s", s); 

            printf("number is %d, string is:%s", i, s);

            ……

             

            1 變參函數(shù)實(shí)現(xiàn)原理

                C調(diào)用約定下可使用va_list系列變參宏實(shí)現(xiàn)變參函數(shù),此處va意為variable-argument(可變參數(shù))。典型用法如下:

            #include <stdarg.h>

            int VarArgFunc(int dwFixedArg, ...){ //以固定參數(shù)的地址為起點(diǎn)依次確定各變參的內(nèi)存起始地址

                va_list pArgs = NULL;  //定義va_list類型的指針pArgs,用于存儲(chǔ)參數(shù)地址

                va_start(pArgs, dwFixedArg); //初始化pArgs指針,使其指向第一個(gè)可變參數(shù)。該宏第二個(gè)參數(shù)是變參列表的前一個(gè)參數(shù),即最后一個(gè)固定參數(shù)

                int dwVarArg = va_arg(pArgs, int); //該宏返回變參列表中的當(dāng)前變參值并使pArgs指向列表中的下個(gè)變參。該宏第二個(gè)參數(shù)是要返回的當(dāng)前變參類型

                //若函數(shù)有多個(gè)可變參數(shù),則依次調(diào)用va_arg宏獲取各個(gè)變參

                va_end(pArgs);  //將指針pArgs置為無(wú)效,結(jié)束變參的獲取

                /* Code Block using variable arguments */

            }

            //可在頭文件中聲明函數(shù)為extern int VarArgFunc(int dwFixedArg, ...);,調(diào)用時(shí)用VarArgFunc(FixedArg, VarArg);

                 變參宏根據(jù)堆棧生長(zhǎng)方向和參數(shù)入棧特點(diǎn),從最靠近第一個(gè)可變參數(shù)的固定參數(shù)開(kāi)始,依次獲取每個(gè)可變參數(shù)的地址。

                 變參宏的定義和實(shí)現(xiàn)因操作系統(tǒng)、硬件平臺(tái)及編譯器而異(但原理相似)。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編譯器使用內(nèi)置宏間接實(shí)現(xiàn)變參宏,如#define va_start(v,l)  __builtin_va_start(v,l)。因?yàn)間cc編譯器需要考慮跨平臺(tái)處理,而其實(shí)現(xiàn)因平臺(tái)而異。例如x86-64或PowerPC處理器下,參數(shù)不全都通過(guò)堆棧傳遞,變參宏的實(shí)現(xiàn)相比x86處理器更為復(fù)雜。

                 x86平臺(tái)VC6.0編譯器中,stdarg.h頭文件內(nèi)變參宏定義如下:

            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, type)    ( *(type *)((ap += _INTSIZEOF(type)) - _INTSIZEOF(type)) )

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

                 各宏的含義如下:

                 ①_INTSIZEOF宏考慮到某些系統(tǒng)需要內(nèi)存地址對(duì)齊。從宏名看應(yīng)按照sizeof(int)即堆棧粒度對(duì)齊,即參數(shù)在內(nèi)存中的地址均為sizeof(int)=4的倍數(shù)。例如,若在1≤sizeof(n)≤4,則_INTSIZEOF(n)=4;若5≤sizeof(n)≤8,則_INTSIZEOF(n)=8。

                 為便于理解,簡(jiǎn)化該宏為

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

            x = sizeof(int) - 1 = 3 = 0b’0000 0000 0000 0011

            ~x = 0b’1111 1111 1111 1100

                 一個(gè)數(shù)與(~x)相與的結(jié)果是sizeof(int)的倍數(shù),即_INTSIZEOF(n)將n圓整為sizeof(int)的倍數(shù)。

                 ②va_start宏根據(jù)(va_list)&v得到第一個(gè)可變參數(shù)前的一個(gè)固定參數(shù)在堆棧中的內(nèi)存地址,加上_INTSIZEOF(v)即v所占內(nèi)存大小后,使ap指向固定參數(shù)后下個(gè)參數(shù)(第一個(gè)可變參數(shù)地址)。

                 固定參數(shù)的地址用于va_start宏,因此不能聲明為寄存器變量(地址無(wú)效)或作為數(shù)組類型(長(zhǎng)度難定)。

                 ③va_arg宏取得type類型的可變參數(shù)值。首先ap+=_INTSIZEOF(type),即ap跳過(guò)當(dāng)前可變參數(shù)而指向下個(gè)變參的地址;然后ap-_INTSIZEOF(type)得到當(dāng)前變參的內(nèi)存地址,類型轉(zhuǎn)換后返回當(dāng)前變參值。

                 va_arg宏的等效實(shí)現(xiàn)如下

            //將指針移動(dòng)至下個(gè)變參,并返回左移的值[-1](數(shù)組下標(biāo)表示偏移量),即當(dāng)前變參值

            #define va_arg(ap,type)  ((type *)((ap) += _INTSIZEOF(type)))[-1]

                 ④va_end宏使ap不再指向有效的內(nèi)存地址。該宏的某些實(shí)現(xiàn)定義為((void*)0),編譯時(shí)不會(huì)為其產(chǎn)生代碼,調(diào)用與否并無(wú)區(qū)別。但某些實(shí)現(xiàn)中va_end宏用于函數(shù)返回前完成一些必要的清理工作:如va_start宏可能以某種方式修改堆棧,導(dǎo)致返回操作無(wú)法完成,va_end宏可將有關(guān)修改復(fù)原;又如va_start宏可能對(duì)參數(shù)列表動(dòng)態(tài)分配內(nèi)存以便于遍歷va_list,va_end宏可釋放此前動(dòng)態(tài)分配的內(nèi)存。因此,從使用va_start宏的函數(shù)中退出之前,必須調(diào)用一次va_end宏。

                 函數(shù)內(nèi)可多次遍歷可變參數(shù),但每次必須以va_start宏開(kāi)始,因?yàn)楸闅v后ap指針不再指向首個(gè)變參。

                 下圖給出基于變參宏的可變參數(shù)在堆棧中的分布:

             

                 變參宏無(wú)法智能識(shí)別可變參數(shù)的數(shù)目和類型,因此實(shí)現(xiàn)變參函數(shù)時(shí)需自行判斷可變參數(shù)的數(shù)目和類型。前者可顯式提供變參數(shù)目或設(shè)定遍歷結(jié)束條件(如-1、'\0'或回車符等)。后者可顯式提供變參類型枚舉值,或在固定參數(shù)中包含足夠的類型信息(如printf函數(shù)通過(guò)分析format字符串即可確定各變參類型),甚至主調(diào)函數(shù)和被調(diào)函數(shù)可約定變參的類型組織等。

             

            2 變參函數(shù)代碼示例

                 本節(jié)給出若干遵循ANSI C標(biāo)準(zhǔn)形式的簡(jiǎn)單可變參數(shù)函數(shù),基于這些示例可構(gòu)造更為復(fù)雜實(shí)用的功能。

                 示例函數(shù)必須包含stdio.h和stdarg.h頭文件,并按需包含string.h頭文件。

                【示例1】函數(shù)接受一個(gè)整型固定參數(shù)和一個(gè)整型可變參數(shù),并打印這兩個(gè)參數(shù)值。

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

                 分別采用以下三種方法調(diào)用:

                 1) IntegerVarArgFunc(10);

                 輸出i=10, j=6803972(形參i的堆棧上方內(nèi)容)

                 2) IntegerVarArgFunc(10, 20);

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

                 3) IntegerVarArgFunc(10, 20, 30);

                 輸出i=10, j=20,多余的變參被忽略。

             

                【示例2】函數(shù)通過(guò)固定參數(shù)指定可變參數(shù)個(gè)數(shù),循環(huán)打印所有變參值。

             1 //第一個(gè)參數(shù)定義可變參數(shù)個(gè)數(shù),用于循環(huán)獲取變參內(nèi)容
             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 }
            View Code

                 調(diào)用方式為ParseVarArgByNum(3, 11, 22, 33);,輸出:

                 The 1th Argument: 11

                 The 2th Argument: 22

                 The 3th Argument: 33

             

                【示例3】函數(shù)定義一個(gè)結(jié)束標(biāo)記,調(diào)用時(shí)通過(guò)最后一個(gè)參數(shù)傳遞該標(biāo)記,以結(jié)束變參的遍歷打印。

             1 //最后一個(gè)參數(shù)作為變參結(jié)束符(-1),用于循環(huán)獲取變參內(nèi)容
             2 void ParseVarArgByEnd(int dwStart, ...){
             3     va_list pArgs = NULL;
             4     va_start(pArgs, dwStart);
             5     int dwArgIdx = 0;
             6     int dwArgVal = dwStart;
             7     while(dwArgVal != -1){
             8         ++dwArgIdx;
             9         printf("The %dth Argument: %d\n",dwArgIdx, dwArgVal);
            10         dwArgVal = va_arg(pArgs, int); //得到下個(gè)變參值
            11     }
            12     va_end(pArgs);
            13 }
            View Code

                 調(diào)用方式為ParseVarArgByEnd(44, 55, -1);,輸出:

                 The 1th Argument: 44

                 The 2th Argument: 55

             

                【示例4】函數(shù)自定義一些可能出現(xiàn)的參數(shù)類型,在變參列表中顯式指定變參類型??蛇@樣傳遞參數(shù):參數(shù)數(shù)目,可變參數(shù)類型1,可變參數(shù)值1,可變參數(shù)類型2,可變參數(shù)值2,....。

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

                 調(diào)用方式為ParseVarArgType(2, INT_TYPE, 222, STR_TYPE, "HelloWorld!");,輸出:

                 The 1th Argument: 222

                 The 2th Argument: HelloWorld!

             

                【示例5】實(shí)現(xiàn)簡(jiǎn)易的MyPrintf函數(shù)。該函數(shù)無(wú)返回值,即不記錄輸出的字符數(shù)目;接受"%d"按整數(shù)輸出、"%c"按字符輸出、"%b"按二進(jìn)制輸出,"%%"輸出'%'本身。

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

                 調(diào)用方式為MyPrintf("Binary string of number %d is = %b!\n", 9999, 9999);,輸出:

                 Binary string of number 9999 is = 10011100001111!

                 注意,MyPrintf函數(shù)for循環(huán)語(yǔ)句段旨在自定義格式化輸出(如%b),而非實(shí)現(xiàn)printf庫(kù)函數(shù)本身;否則直接使用vprintf(pszFmt, pArgs);即可。此外該函數(shù)存在一處明顯缺陷,即%b前若出現(xiàn)case匹配項(xiàng)外的控制字符(如%x),則會(huì)調(diào)用vprintf函數(shù)處理該字符及其后的格式串,%b將會(huì)原樣輸出"%b"(而非轉(zhuǎn)換為二進(jìn)制)。

                 本示例中也附帶實(shí)現(xiàn)了MyItoa函數(shù)。該函數(shù)與非標(biāo)準(zhǔn)C語(yǔ)言擴(kuò)展函數(shù)itoa功能相同。該函數(shù)將整數(shù)iValue轉(zhuǎn)換為uiRadix 所指定的進(jìn)制數(shù)字符串,并將其存入pszResBuf字符數(shù)組。

             

                【示例6】可變參數(shù)數(shù)目不多時(shí),可用數(shù)組或結(jié)構(gòu)體數(shù)組變相實(shí)現(xiàn)可變參數(shù)函數(shù)。

            #define VAR_ARG_MAX_NUM    (unsigned char)10
            #define VAR_ARG_MAX_LEN     (unsigned char)20
            //可變參數(shù)信息
            typedef struct{
                E_VAR_TYPE eArgType;
                unsigned char aucArgVal[VAR_ARG_MAX_LEN];
            }VAR_ARG_ENTRY;
            typedef struct{
                unsigned char ucArgNum;
                VAR_ARG_ENTRY aucVarArg[VAR_ARG_MAX_NUM];
            }VAR_ARG_LIST;
            void ParseStructArrayArg(VAR_ARG_LIST *ptVarArgList){
                int i = 0;
                for(i = 0; i < ptVarArgList->ucArgNum; i++){
                    E_VAR_TYPE eArgType = ptVarArgList->aucVarArg[i].eArgType;
                    switch(eArgType){
                        case CHAR_TYPE:
                            printf("The %dth Argument: %c\n", i+1, ptVarArgList->aucVarArg[i].aucArgVal[0]);
                            break;
                        case STR_TYPE:
                            printf("The %dth Argument: %s\n", i+1, ptVarArgList->aucVarArg[i].aucArgVal);
                            break;
                        default:
                            break;
                    }
                }
            }
            View Code

                 調(diào)用方式為

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

            ParseStructArrayArg(&tVarArgList);

                 輸出:

                 The 1th Argument: H

                 The 2th Argument: TEST

                 本示例函數(shù)原型稍加改造,顯式聲明參數(shù)數(shù)目如下:

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

            void ParseStructArrayArg(unsigned char ucArgNum, VAR_ARG_ENTRY *aucVarArg);

                 改造后的原型與main函數(shù)的帶參原型非常相似!

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

            int main(int argc, char **argv);

                 若VAR_ARG_ENTRY內(nèi)的變參數(shù)目和類型固定,則主調(diào)函數(shù)和被調(diào)函數(shù)雙方約定后可采用char型數(shù)組替代VAR_ARG_ENTRY結(jié)構(gòu)體數(shù)組。

                 通過(guò)數(shù)組可替代某些不必要的變參函數(shù)實(shí)現(xiàn),如對(duì)整數(shù)求和:

            實(shí)現(xiàn)方式

            可變參數(shù)函數(shù)

            數(shù)組替代

            函數(shù)代碼

            int SumVarArg(int dwStart, ...){

                va_list pArgs = NULL;

                va_start(pArgs, dwStart);

                int dwArgVal = dwStart, dwSum = 0;

                while(dwArgVal != 0){ //0為結(jié)束標(biāo)志

                    dwSum += dwArgVal;

                    dwArgVal = va_arg(pArgs, int);

                };

                va_end(pArgs);

                return dwSum;

            }

            int SumArray(int aucArr[], int dwSize){

                int i = 0, dwSum = 0;

                for(i = 0; i < dwSize; i++){

                    dwSum += aucArr[i];

                }

                return dwSum;

            }

            調(diào)用方式

            SumVarArg(7, 2, 7, 11, -2, 0);

            int aucArr[] = {7, 2, 7, 11, -2};

            SumArrayArg(aucArr, sizeof(aucArr)/sizeof(aucArr[0]));

                 數(shù)組方式調(diào)用時(shí)可方便地指定求和項(xiàng)的起止,如SumArrayArg(&aucArr[1], 3)將從數(shù)組aucArr的第2個(gè)元素開(kāi)始累加3個(gè)元素,即2+7+11=20。而這是變參函數(shù)SumVarArg無(wú)法做到的。

             

            3 變參函數(shù)注意事項(xiàng)

                 可變參數(shù)函數(shù)在編程中應(yīng)注意以下問(wèn)題:

                 1) 編譯器對(duì)可變參數(shù)函數(shù)的原型檢查不夠嚴(yán)格,不利于編程查錯(cuò)。

                 調(diào)用變參函數(shù)時(shí),傳遞的變參數(shù)目應(yīng)不少于該函數(shù)所期望的變參數(shù)目(該數(shù)目由主調(diào)函數(shù)實(shí)參指定或由變參函數(shù)內(nèi)部實(shí)現(xiàn)決定),否則會(huì)訪問(wèn)到函數(shù)參數(shù)以外的堆棧區(qū)域,可能導(dǎo)致堆棧錯(cuò)誤。

                 如示例1中可變參數(shù)為char*類型(用%s打印) 時(shí),若使用整型變參調(diào)用該函數(shù),可能會(huì)出現(xiàn)段錯(cuò)誤(Linux)或頁(yè)面非法錯(cuò)誤(Windows),也可能出現(xiàn)難以覺(jué)察的細(xì)微錯(cuò)誤。

                 printf函數(shù)格式化字符串參數(shù)所指定的類型與后面變參的類型不匹配時(shí),也可能造成程序崩潰(尤其以%s打印整型參數(shù)值時(shí))。

                 gcc編譯器提供attribute 機(jī)制用以編譯時(shí)檢查某些變參函數(shù)調(diào)用情況,如聲明函數(shù)為

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

                 表示函數(shù)原型中第2個(gè)參數(shù)(pFmt)為格式化字符串,從參數(shù)列表中第3個(gè)參數(shù)(即首個(gè)變參)開(kāi)始與pFmt形式比較。該聲明將對(duì)OmciLog(LOG_PON, "%s", 1)的調(diào)用產(chǎn)生編譯警告:

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

                 但該機(jī)制主要針對(duì)類似scanf/printf的變參函數(shù),此類函數(shù)可根據(jù)格式化字符串確定變參數(shù)目和類型。

                 2) va_arg(ap, type)宏獲取變參時(shí),type不可指定為以下類型:

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

                 在C語(yǔ)言中,調(diào)用不帶原型聲明或聲明為變參的函數(shù)時(shí),主調(diào)函數(shù)會(huì)在傳遞未顯式聲明的參數(shù)前對(duì)其執(zhí)行“缺省參數(shù)提升(default argument promotions)”,將提升后的參數(shù)值傳遞給被調(diào)函數(shù)。

                 提升操作如下:

            • float類型的參數(shù)提升為double類型
            • char、short和相應(yīng)的signed、unsigned類型參數(shù)提升為int類型
            • 若int類型不能存儲(chǔ)原值,則提升為unsigned int類型

                 在gcc 編譯器中,若type使用char或unsigned short int等需提升的類型,可能會(huì)得到嚴(yán)重警告。 

                 因此,若要獲取變參數(shù)列表中float類型的實(shí)參,則變參函數(shù)中應(yīng)使用double dVar = va_arg(ap, double)或float fVar = (float)va_arg(ap, double)。char和short類型實(shí)參處理方式與之類似。

                 3) 使用va_arg宏獲取變參列表中類型為函數(shù)指針的參數(shù)時(shí),可能需要將函數(shù)指針用typedef定義為新的數(shù)據(jù)類型,以便通過(guò)編譯(與va_arg宏的實(shí)現(xiàn)有關(guān))。

                 對(duì)于VC6.0的va_arg宏實(shí)現(xiàn),若用該宏從變參列表中提取函數(shù)指針類型的參數(shù),如

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

                 被擴(kuò)展為以下形式(為縮減長(zhǎng)度直接寫出_INTSIZEOF宏值)

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

                 顯然,(int (*)() *)無(wú)意義。

                 解決方法如下

            typedef int (*pFunc)();

                 va_arg(argp, pFunc)被擴(kuò)展為(*(pFunc *)((pArgs += 4) - 4)),即可通過(guò)編譯檢查。

                 而在gcc編譯器下,va_arg宏可直接使用函數(shù)指針類型。

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

                 以ParseFuncPtrVarArg(1, "Welcome", DummyFunc);方式調(diào)用,輸出為1 Welcome Here!!!。

                 4) C語(yǔ)言層面上無(wú)法將函數(shù)A的可變參數(shù)直接傳遞給函數(shù)B。只能定義被調(diào)函數(shù)的參數(shù)為va_list類型,在主調(diào)函數(shù)中將可變參數(shù)列表轉(zhuǎn)換為va_list,再進(jìn)行可變參數(shù)的傳遞。這種技巧常用于定制打印函數(shù):

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

                 其中被調(diào)函數(shù)vsnprintf可根據(jù)va_arg(pszFmt, pArgs)依次取出所需的變參。

                 以O(shè)mciLog("%d %f %s\n", 10, 20.3, "ABC");方式調(diào)用,輸出為10 20.300000 ABC。

                 5) 可變參數(shù)必須從頭到尾按照順序逐個(gè)訪問(wèn)??稍L問(wèn)幾個(gè)變參后中止,但不能一開(kāi)始就訪問(wèn)變參列表中間的參數(shù)。

                 6) ANSI C要求至少定義一個(gè)固定參數(shù)(ISO C requires a named argument before '...'),該參數(shù)將傳遞給va_start宏以查找參數(shù)列表的可變部分。故不可定義void func(...)這樣的函數(shù)。

                 7) 變參宏實(shí)現(xiàn)與堆棧相關(guān),在參數(shù)入寄存器的處理器下實(shí)現(xiàn)可能異常復(fù)雜(gcc中va_start宏會(huì)將所有可能用于變參傳遞的寄存器均保存在棧中)。因此如非必要,應(yīng)盡量避免使用變參宏。C語(yǔ)言中除示例6中數(shù)組或結(jié)構(gòu)體數(shù)組替代方式外,還可采用回調(diào)函數(shù)方式"拋出"變化部分,如:

             1 /**********************************************************************
             2 * 函數(shù)名稱: OmciLocateListNode
             3 * 功能描述: 查找鏈表首個(gè)與pData滿足函數(shù)fCompareNode判定關(guān)系的結(jié)點(diǎn)
             4 * 輸入?yún)?shù): T_OMCI_LIST* pList           :鏈表指針
             5 *            VOID* pData                  :待比較數(shù)據(jù)指針
             6 *            CompareNodeFunc fCompareNode :比較回調(diào)函數(shù)指針
             7 * 輸出參數(shù): NA
             8 * 返 回 值: T_OMCI_LIST_NODE* 鏈表結(jié)點(diǎn)指針(未找到時(shí)返回NULL)
             9 ***********************************************************************/
            10 T_OMCI_LIST_NODE* OmciLocateListNode(T_OMCI_LIST *pList, VOID *pData, CompareNodeFunc fCompareNode)
            11 {
            12     CHECK_TRIPLE_POINTER(pList, pData, fCompareNode, NULL);
            13     CHECK_SINGLE_POINTER(pList->pHead, NULL);
            14     CHECK_SINGLE_POINTER(pList->pHead->pNext, NULL);
            15 
            16     if(0 == pList->dwNodeNum)
            17     {
            18         return NULL;
            19     }
            20 
            21     T_OMCI_LIST_NODE *pListNode = pList->pHead->pNext;
            22     while(pListNode != pList->pHead)
            23     {
            24         if(0 == fCompareNode(pListNode->pNodeData, pData, pList->dwNodeDataSize))
            25             return pListNode;
            26 
            27         pListNode = pListNode->pNext;
            28     }
            29 
            30     return NULL;
            31 }
            View Code

                 OmciLocateListNode函數(shù)是下面Omci_List_Query函數(shù)的另一實(shí)現(xiàn)。主調(diào)函數(shù)提供fCompareNode回調(diào)函數(shù)以比較鏈表結(jié)點(diǎn),從而簡(jiǎn)化代碼實(shí)現(xiàn),并增強(qiáng)可讀性。

             1 /***************************************************************
             2  * Function: Omci_List_Query
             3  * Description -
             4  *     根據(jù)給定的KEY偏移和KEY長(zhǎng)度,查找目標(biāo)節(jié)點(diǎn)
             5  * Input: 
             6  *     pList: 鏈表
             7  *     可變參數(shù): 三個(gè)參數(shù)為一組,第一個(gè)為key value,第二個(gè)為key
             8  *               偏移,第三個(gè)為key長(zhǎng)度,以LIST_END表示參數(shù)結(jié)束。
             9  * Output: 
            10  * Returns: 
            11  * 
            12  * modification history
            13  * -------------------------------
            14  * Created : 2011-5-25 by xxx
            15  * ------------------------------
            16  ***************************************************************/
            17 OMCI_LIST_NODE* Omci_List_Query(OMCI_LIST *pList, ...)
            18 {
            19     OMCI_LIST_NODE_KEY  aKeyGroup[MAX_LIST_NODE_KEYS_NUM];
            20     OMCI_LIST_NODE *pNode=NULL;
            21     INT8U *pData=NULL, *pKeyValue=NULL;
            22     INT8U ucKeyNum=0, i;
            23     INT32U iKeyOffset=0, iKeyLen=0;
            24     VA_LIST tArgList;
            25 
            26     if(NULL==pList)
            27         return NULL;
            28     memset((INT8U*)aKeyGroup, 0, sizeof(OMCI_LIST_NODE_KEY)*MAX_LIST_NODE_KEYS_NUM);
            29     VA_START(tArgList, pList);
            30     while(TRUE)
            31     {
            32         pKeyValue=VA_ARG(tArgList, INT8U*);
            33         if(LIST_END==pKeyValue)
            34             break;
            35         iKeyOffset=VA_ARG(tArgList, INT32U);
            36         iKeyLen=VA_ARG(tArgList, INT32U);
            37         if(0==iKeyLen)
            38         {
            39             VA_END(tArgList);
            40             return NULL;
            41         }
            42         if(ucKeyNum>=MAX_LIST_NODE_KEYS_NUM)
            43         {
            44             VA_END(tArgList);
            45             return NULL;
            46         }
            47         aKeyGroup[ucKeyNum].pKeyValue=pKeyValue;
            48         aKeyGroup[ucKeyNum].iKeyOffset=iKeyOffset;
            49         aKeyGroup[ucKeyNum++].iKeyLen=iKeyLen;
            50     }
            51     VA_END(tArgList);
            52 
            53     pNode=Omci_List_First(pList);
            54     while(NULL!=pNode)
            55     {
            56         pData=(INT8U*)pNode->pNodeData;
            57         for(i=0; i<ucKeyNum; i++)
            58         {
            59             if(0!=memcmp(&pData[aKeyGroup[i].iKeyOffset], aKeyGroup[i].pKeyValue, aKeyGroup[i].iKeyLen))
            60                 break;
            61         }
            62         if(i>=ucKeyNum)
            63         {
            64             break;
            65         }
            66         pNode=pNode->pNext;
            67     }
            68     return pNode;
            69 }
            View Code

                 在C++語(yǔ)言里,可利用多態(tài)性來(lái)實(shí)現(xiàn)可變參數(shù)的功能(但靈活性有所下降)。 

             

            【擴(kuò)展閱讀】vsnprintf函數(shù)

            vsnprintf函數(shù)原型為:int vsnprintf(char *str, size_t size, const char *format, va_list ap)。

            該函數(shù)將根據(jù)format字符串來(lái)轉(zhuǎn)換并格式化ap所指向的可變參數(shù)列表,并將結(jié)果字符串以不超過(guò)size字節(jié)(包括字符串結(jié)束符'\0')的長(zhǎng)度寫入str所指向的字符串緩沖區(qū)(該緩沖區(qū)大小至少為size字節(jié))。若結(jié)果字符串超過(guò)size-1個(gè)字符,則丟棄多余字節(jié),但將其計(jì)入函數(shù)返回值。若函數(shù)執(zhí)行成功,則返回實(shí)際或本該寫入的字符數(shù)目(包括字符串結(jié)束符);否則將返回負(fù)值。因此,僅當(dāng)返回值為小于size的非負(fù)值時(shí),表明結(jié)果字符串被完全寫入(大于等于size則意味著字符串被截?cái)?。snprintf函數(shù)的返回值規(guī)則與之相同。

            注意,當(dāng)目的緩沖區(qū)不夠大時(shí)會(huì)截?cái)嘧址?,但vsnprintf/snprintf函數(shù)確保緩沖區(qū)中存放的字符串以NULL結(jié)尾,而stncpy函數(shù)處理后的字符串不含結(jié)束符。

             

            亚洲伊人久久大香线蕉苏妲己| 国产精品女同一区二区久久| 亚洲一区中文字幕久久| 久久香蕉国产线看观看99| 久久久久亚洲爆乳少妇无| 国内精品伊人久久久影院 | 理论片午午伦夜理片久久| 模特私拍国产精品久久| 国产精品久久午夜夜伦鲁鲁| 久久综合给合综合久久| 久久99国产精品久久99果冻传媒| 久久伊人影视| 伊人色综合久久天天| 伊人久久大香线蕉av不变影院| 亚洲国产成人久久精品动漫| 中文字幕乱码久久午夜| 国产69精品久久久久99| 久久综合给合久久国产免费| 日韩久久久久中文字幕人妻| 久久精品国产免费| 性欧美丰满熟妇XXXX性久久久| 久久丝袜精品中文字幕| 爱做久久久久久| 久久精品国产亚洲网站| 精品久久久久久成人AV| 久久久久久伊人高潮影院| 久久影视综合亚洲| 久久精品国产一区二区三区不卡 | 欧美性猛交xxxx免费看久久久| 亚洲精品无码久久久久久| 亚洲国产成人久久综合区| 久久精品国产只有精品66| 久久久久久久综合综合狠狠| 国产精品99久久久久久www| 91精品国产综合久久四虎久久无码一级 | 色偷偷久久一区二区三区| 国产成人久久777777| 国产V综合V亚洲欧美久久| 久久久av波多野一区二区| 久久久精品2019免费观看| 亚洲午夜久久久影院伊人|