2010-04-08 119 views
27

有什麼辦法可以計算長度爲va_list?所有的例子我都看到了變量參數的數量。使用變量列表參數時va_list的長度?

+0

我看到的'va_arg'的每個實現只是移動指針'sizeof(arg_type)'個字節,所以無法知道長度;那是你的工作。 – 2013-09-03 05:22:17

回答

24

有沒有辦法計算一個va_list的長度,這就是爲什麼你需要類似於函數printf中的格式字符串。

唯一 功能,可與工作 宏的va_list are

  • va_start - 開始使用va_list
  • va_arg - 獲得一個參數
  • va_end - 停止使用va_list
  • va_copy (因爲C++ 11和C99) - 複製va_list

請注意,你需要調用va_startva_end在同一範圍內,這意味着你不能在其析構函數調用在其構造va_startva_end一個工具類,把它包裝(我被這一朝被蛇咬)。

例如這個類是毫無價值:

class arg_list { 
    va_list vl; 
public: 
    arg_list(const int& n) { va_start(vl, n); } 
    ~arg_list() { va_end(vl); } 
    int arg() { 
     return static_cast<int>(va_arg(vl, int); 
    } 
}; 

GCC輸出following error

t.cpp: In constructor arg_list::arg_list(const int&) :
Line 7: error: va_start used in function with fixed args
compilation terminated due to -Wfatal-errors.

+4

嗯?不,可以調用'va_start()',然後將'va_arg'傳遞給另一個函數,然後調用'va_end()'。這正是你如何使用'vsprintf()'和類似的功能。 – caf 2010-04-08 07:36:25

+1

@caf:在使用vsprintf()的情況下,va_list-instance在什麼樣的Universe中轉義範圍? – 2010-04-08 07:44:08

+0

@caf你是對的,我沒有清楚地表達出來,你不必在'va_start'範圍內調用'va_arg'。我澄清了我的發言。 – Motti 2010-04-08 07:50:46

8

有一個可變參數函數來確定很多參數是如何傳遞沒有直接方式。 (至少沒有便攜的方式; <stdarg.h>接口不提供該信息。)

有幾種間接方式。

兩個最常見的有:

  • 格式字符串(指定,通過你可以稱之爲一個小的簡單的語言,其餘參數的數量和類型)。 *printf()*scanf()函數族使用這種機制。
  • 表示參數結束的標記值。一些Unix/POSIX exec*()功能家族這樣做,使用空指針來標記參數的結尾。

但也有其他可能性:

  • 更簡單地說,一個整數計數,指定以下參數的數量;大概在這種情況下,他們都是同一類型的。
  • 交替參數,其中參數可以是指定以下參數類型的枚舉值。一個假設的例子可能看起來像:
    func(ARG_INT, 42, ARG_STRING, "foo", ARG_DOUBLE, 1.25, ARG_END);
    甚至:
    func("-i", 42, "-s", "foo", "-d", 1.25, "");
    如果你想效仿參數通常傳遞給Unix命令的方式。

你甚至可以分配一個值到全局變量指定的參數個數:

func_arg_count = 3; 
func(1, 2, 3); 

這將是醜陋的,但完全合法的。

在所有這些技術中,傳遞一致參數完全是調用者的責任;被調用者只能使用假設其參數是正確的。

請注意,可變參數函數不需要處理傳遞給它的所有參數。例如,這樣的:

printf("%d\n", 10, 20); 

將打印10,靜靜地忽略20。很少有任何理由利用該功能。

10

尚未提及的一種方法是使用預處理器宏使用va_list長度作爲第一個參數調用variadict函數,並且還沿參數向前。這有點「可愛」的解決方案,但不需要手動輸入參數列表長度。

假設你有以下功能:

int Min(int count, ...) { 
    va_list args; 
    va_start(args, count); 

    int min = va_arg(args, int); 
    for (int i = 0; i < count-1; ++i) { 
     int next = va_arg(args, int); 
     min = min < next ? min : next; 
    } 
    va_end(args); 

    return min; 
} 

的想法是,你必須能夠通過使用一個掩碼__VA_ARGS__計算的參數個數的預處理宏。有確定__VA_ARGS__長度包括P99和Boost預處理器幾個不錯的預處理程序庫,但只是讓我不要在這個答案留孔,下面是它可以做到:

#define IS_MSVC _MSC_VER && !__INTEL_COMPILER 

/** 
* Define the macros to determine variadic argument lengths up to 20 arguments. The MSVC 
* preprocessor handles variadic arguments a bit differently than the GNU preprocessor, 
* so we account for that here. 
*/ 
#if IS_MSVC 
    #define MSVC_HACK(FUNC, ARGS) FUNC ARGS 
    #define APPLY(FUNC, ...) MSVC_HACK(FUNC, (__VA_ARGS__)) 
    #define VA_LENGTH(...) APPLY(VA_LENGTH_, 0, ## __VA_ARGS__, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) 
#else 
    #define VA_LENGTH(...) VA_LENGTH_(0, ## __VA_ARGS__, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) 
#endif 

/** 
* Strip the processed arguments to a length variable. 
*/ 
#define VA_LENGTH_(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, N, ...) N 

注意:一上面的很多噪音都是針對MSVC的解決方案。

利用上述所定義,可以創建單個宏來執行所有基於長度的操作:

/** 
* Use the VA_LENGTH macro to determine the length of the variadict args to 
* pass in as the first parameter, and forward along the arguments after that. 
*/ 
#define ExecVF(Func, ...) Func(VA_LENGTH(__VA_ARGS__), __VA_ARGS__) 

該宏能夠調用任何variadict函數,只要它始於int count參數。總之,而不是使用:

int result = Min(5, 1, 2, 3, 4, 5); 

您可以使用:

int result = ExecVF(Min, 1, 2, 3, 4, 5); 

這裏的閩的模板版本,它使用相同的方法:https://gist.github.com/mbolt35/4e60da5aaec94dcd39ca

+2

非常可愛。 Min的第3行應該說「count」而不是「value」? – Amoss 2014-02-14 07:43:06

+0

更新 - 謝謝指出! – mbolt35 2014-02-23 00:45:33

2

你可以嘗試,如果使用功能_vscprintf你在MS Visual Studio下工作。 這裏是一個如何使用_vscprintf的例子,我用它來知道我的控制檯標題需要多少空間到malloc。

int SetTitle(const char *format,...){ 
    char *string; 
    va_list arguments; 

    va_start(arguments,format); 
     string=(char *)malloc(sizeof(char)*(_vscprintf(format,arguments)+1)); 
     if(string==NULL) 
      SetConsoleTitle("Untitled"); 
     else 
      vsprintf(string,format,arguments); 
    va_end(arguments); 

    if(string==NULL) 
     return SETTITLE_MALLOCFAILED; 
    SetConsoleTitle(string); 
    free(string); 
    return 0; 
} 

或者你也可以做到這一點,添加輸出到臨時文件,然後從中讀取數據到分配的內存就像我在接下來的例子中那樣:

void r_text(const char *format, ...){ 
    FILE *tmp = tmpfile(); 
    va_list vl; 
    int len; 
    char *str; 

    va_start(vl, format); 
     len = vfprintf(tmp, format, vl); 
    va_end(vl); 
    rewind(tmp); 
    str = (char *) malloc(sizeof(char) * len +1); 
    fgets(str, len+1, tmp); 
    printf("%s",str); 
    free(str); 
    fclose(tmp); 
} 
+0

如果您提供了一些相關的代碼,說明您的建議是如何解決問題的,那會更好。您可以參考其他答案作爲指導。 – MasterAM 2014-02-09 18:15:52

+0

@MasterAM更新了我的回答 – Ruza 2014-04-18 20:09:17

+0

@MasterAM增加了另一個例子 – Ruza 2014-05-14 16:34:02