可變參數函數詳解


 

     可變參數函數又稱參數個數可變函數(本文也簡稱變參函數),即函數參數數目可變。原型聲明格式為:

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

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

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

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

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

     printf函數除參數format固定外,后續參數的數目和類型均可變。實際調用時可有以下形式:

printf("string"); 

printf("%d", i); 

printf("%s", s); 

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

……

 

1 變參函數實現原理

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

#include <stdarg.h>

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

    va_list pArgs = NULL;  //定義va_list類型的指針pArgs,用於存儲參數地址

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

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

    //若函數有多個可變參數,則依次調用va_arg宏獲取各個變參

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

    /* Code Block using variable arguments */

}

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

     變參宏根據堆棧生長方向和參數入棧特點,從最靠近第一個可變參數的固定參數開始,依次獲取每個可變參數的地址。

     變參宏的定義和實現因操作系統、硬件平台及編譯器而異(但原理相似)。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;

#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宏考慮到某些系統需要內存地址對齊。從宏名看應按照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))

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

~x = 0b’1111 1111 1111 1100

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

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

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

     ③va_arg宏取得type類型的可變參數值。首先ap+=_INTSIZEOF(type),即ap跳過當前可變參數而指向下個變參的地址;然后ap-_INTSIZEOF(type)得到當前變參的內存地址,類型轉換后返回當前變參值。

     va_arg宏的等效實現如下

//將指針移動至下個變參,並返回左移的值[-1](數組下標表示偏移量),即當前變參值

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

     ④va_end宏使ap不再指向有效的內存地址。該宏的某些實現定義為((void*)0),編譯時不會為其產生代碼,調用與否並無區別。但某些實現中va_end宏用於函數返回前完成一些必要的清理工作:如va_start宏可能以某種方式修改堆棧,導致返回操作無法完成,va_end宏可將有關修改復原;又如va_start宏可能對參數列表動態分配內存以便於遍歷va_list,va_end宏可釋放此前動態分配的內存。因此,從使用va_start宏的函數中退出之前,必須調用一次va_end宏。

     函數內可多次遍歷可變參數,但每次必須以va_start宏開始,因為遍歷后ap指針不再指向首個變參。

     下圖給出基於變參宏的可變參數在堆棧中的分布:

 

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

 

2 變參函數代碼示例

     本節給出若干遵循ANSI C標准形式的簡單可變參數函數,基於這些示例可構造更為復雜實用的功能。

     示例函數必須包含stdio.h和stdarg.h頭文件,並按需包含string.h頭文件。

    【示例1】函數接受一個整型固定參數和一個整型可變參數,並打印這兩個參數值。

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

     分別采用以下三種方法調用:

     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 //第一個參數定義可變參數個數,用於循環獲取變參內容
 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

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

     The 1th Argument: 11

     The 2th Argument: 22

     The 3th Argument: 33

 

    【示例3】函數定義一個結束標記,調用時通過最后一個參數傳遞該標記,以結束變參的遍歷打印。

 1 //最后一個參數作為變參結束符(-1),用於循環獲取變參內容
 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); //得到下個變參值
11     }
12     va_end(pArgs);
13 }
View Code

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

     The 1th Argument: 44

     The 2th Argument: 55

 

    【示例4】函數自定義一些可能出現的參數類型,在變參列表中顯式指定變參類型。可這樣傳遞參數:參數數目,可變參數類型1,可變參數值1,可變參數類型2,可變參數值2,....。

 1 //可變參數采用<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

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

     The 1th Argument: 222

     The 2th Argument: HelloWorld!

 

    【示例5】實現簡易的MyPrintf函數。該函數無返回值,即不記錄輸出的字符數目;接受"%d"按整數輸出、"%c"按字符輸出、"%b"按二進制輸出,"%%"輸出'%'本身。

 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 '%': //連續兩個'%'輸出單個'%'
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': {//按照二進制輸出
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

     調用方式為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】可變參數數目不多時,可用數組或結構體數組變相實現可變參數函數。

#define VAR_ARG_MAX_NUM    (unsigned char)10
#define VAR_ARG_MAX_LEN     (unsigned char)20
//可變參數信息
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

     調用方式為

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

ParseStructArrayArg(&tVarArgList);

     輸出:

     The 1th Argument: H

     The 2th Argument: TEST

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

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

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

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

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

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

     若VAR_ARG_ENTRY內的變參數目和類型固定,則主調函數和被調函數雙方約定后可采用char型數組替代VAR_ARG_ENTRY結構體數組。

     通過數組可替代某些不必要的變參函數實現,如對整數求和:

實現方式

可變參數函數

數組替代

函數代碼

int SumVarArg(int dwStart, ...){

    va_list pArgs = NULL;

    va_start(pArgs, dwStart);

    int dwArgVal = dwStart, dwSum = 0;

    while(dwArgVal != 0){ //0為結束標志

        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;

}

調用方式

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

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

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

     數組方式調用時可方便地指定求和項的起止,如SumArrayArg(&aucArr[1], 3)將從數組aucArr的第2個元素開始累加3個元素,即2+7+11=20。而這是變參函數SumVarArg無法做到的。

 

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 //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);方式調用,輸出為1 Welcome Here!!!。

     4) C語言層面上無法將函數A的可變參數直接傳遞給函數B。只能定義被調函數的參數為va_list類型,在主調函數中將可變參數列表轉換為va_list,再進行可變參數的傳遞。這種技巧常用於定制打印函數:

 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

     其中被調函數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 /**********************************************************************
 2 * 函數名稱: OmciLocateListNode
 3 * 功能描述: 查找鏈表首個與pData滿足函數fCompareNode判定關系的結點
 4 * 輸入參數: T_OMCI_LIST* pList           :鏈表指針
 5 *            VOID* pData                  :待比較數據指針
 6 *            CompareNodeFunc fCompareNode :比較回調函數指針
 7 * 輸出參數: NA
 8 * 返 回 值: T_OMCI_LIST_NODE* 鏈表結點指針(未找到時返回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函數是下面Omci_List_Query函數的另一實現。主調函數提供fCompareNode回調函數以比較鏈表結點,從而簡化代碼實現,並增強可讀性。

 1 /***************************************************************
 2  * Function: Omci_List_Query
 3  * Description -
 4  *     根據給定的KEY偏移和KEY長度,查找目標節點
 5  * Input: 
 6  *     pList: 鏈表
 7  *     可變參數: 三個參數為一組,第一個為key value,第二個為key
 8  *               偏移,第三個為key長度,以LIST_END表示參數結束。
 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++語言里,可利用多態性來實現可變參數的功能(但靈活性有所下降)。 

 

【擴展閱讀】vsnprintf函數

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

該函數將根據format字符串來轉換並格式化ap所指向的可變參數列表,並將結果字符串以不超過size字節(包括字符串結束符'\0')的長度寫入str所指向的字符串緩沖區(該緩沖區大小至少為size字節)。若結果字符串超過size-1個字符,則丟棄多余字節,但將其計入函數返回值。若函數執行成功,則返回實際或本該寫入的字符數目(包括字符串結束符);否則將返回負值。因此,僅當返回值為小於size的非負值時,表明結果字符串被完全寫入(大於等於size則意味着字符串被截斷)。snprintf函數的返回值規則與之相同。

注意,當目的緩沖區不夠大時會截斷字符串,但vsnprintf/snprintf函數確保緩沖區中存放的字符串以NULL結尾,而stncpy函數處理后的字符串不含結束符。

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM