C語言函數可變參數的祕密

C語言中類似printf()函數這樣支持可變參數的功能是如何實現的?這要歸功於va_start,va_arg,va_end等幾個宏的支持。

 

這個幾個宏定義如下:

 

#define _INTSIZEOF(n)   ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) ) // 此句宏的作用是將類型n的大小向上取成4的倍數,如n爲char型的話結果即爲4

#ifdef  __cplusplus
#define _ADDRESSOF(v)   ( &reinterpret_cast<const char &>(v) ) // vs2015中此句高亮,作用是將v的地址重新解釋成char*型
#else
#define _ADDRESSOF(v)   ( &(v) )
#endif

#define _crt_va_start(ap,v)  ( ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v) )
#define _crt_va_arg(ap,t)    ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
#define _crt_va_end(ap)      ( ap = (va_list)0 )

#ifndef _VA_LIST_DEFINED
#ifdef _M_CEE_PURE
typedef System::ArgIterator va_list;
#else
typedef char *  va_list;      // vs2015中此句高亮
#endif /* _M_CEE_PURE */
#define _VA_LIST_DEFINED
#endif
// 以上宏定義出現在vadef.h,通過stdio.h即可使用
#define va_start _crt_va_start
#define va_arg _crt_va_arg
#define va_end _crt_va_end
// 以上宏定義出現在stdarg.h中,若要使用則需加上 #include <stdarg.h>

具體使用例子:

#include <stdio.h>
#include <string.h>
#include <stdarg.h> // 必需頭文件

int demo(char *, ...);
int  main()
{

    demo("DEMO", "This", "is", "a", 1, 2.0);
    return 0;
}

int demo(char *msg, ...)
{
    va_list argp; // 必需,其實就是第一個指針,會依次指向各參數。
    int argno = 0;
    char *para;
    va_start(argp, msg); // 使argp指向第二個參數,即msg之後的參數

    para = va_arg(argp, char*); // 兩個效果:1,先把argp當前指向的參數傳給para;2、然後使argp指向下一個參數。注意va_arg()的第二個參數類型要與實際參數類型一致。
    printf("Parameter 2 is: %s\n", para);
    para = va_arg(argp, char*); // 第三個參數是char*類型,所以va_arg()第二個參數也必須是char*類型。
    printf("Parameter 3 is: %s\n", para);
    para = va_arg(argp, char*);
    printf("Parameter 4 is: %s\n", para);
    int arg1 = va_arg(argp, int);// 第五個參數是int類型,所以va_arg()第二個參數也必須是int類型。
    printf("Parameter 5 is: %d\n", arg1);
    float arg2 = va_arg(argp, double);// 第六個參數是float類型,va_arg()第二個參數必需是double類型,暫時不曉得爲什麼
    printf("Parameter 6 is: %f\n", arg2);
    va_end(argp); // 將argp置爲空
    return 0;
}

輸出:

Parameter 2 is: This
Parameter 3 is: is
Parameter 4 is: a
Parameter 5 is: 1
Parameter 6 is: 2.000000

 

總結:

  1. 需要包含頭文件<stdarg.h>,要用到va_list, va_start, va_arg, va_end這幾個宏(注意,這些都是宏定義哦);

  2. 上述幾個宏要保證使用順序;

  3. va_arg()第二個參數類型一定要與當前指向的參數類型一致(注意搞清楚當前指向的是哪個參數);

  4. 使用技巧上,可變參數函數的第一個參數可以用來記錄函數調用時可變參數的個數。比如demo()裏第一個參數msg可以聲明爲int類型,如果後面有5個可變參數,就賦值爲5,這樣方便使用循環來讀取參數。實際調用效果如下:

int demo(int msg, ...);

int main()
{
    demo(5, "This", "is", "a", 1, 2.0);
    return 0;
}

//......

 

 

 

5. printf()函數是先解析第一個格式字符串,來確定有幾個參數,分別是什麼類型。

參考資料:

  1. https://www.cnblogs.com/avota/p/4807188.html

  2. http://www.cnblogs.com/hanyonglu/archive/2011/05/07/2039916.html 此文章中源碼有問題,注意看評論

  3. CSDN: va_start/va_arg/va_end/va_list

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章