簡體   English   中英

使用變量列表參數時va_list的長度?

[英]length of va_list when using variable list arguments?

有什么方法可以計算va_list長度嗎? 我看到的所有示例都明確給出了可變參數的數量。

無法計算va_list的長度,這就是為什么在像printf這樣的函數中需要格式字符串的原因。

可用於va_list的唯一 功能

  • va_start開始使用va_list
  • va_arg獲取下一個參數
  • va_end停止使用va_list
  • va_copy (自C ++ 11和C99起)-復制va_list

請注意,您需要在相同的作用域中調用va_startva_end ,這意味着您不能將其包裝在實用程序類中,該實用程序類在其構造函數中調用va_start並在其析構函數中調用va_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輸出以下錯誤

t.cpp:在構造函數arg_list::arg_list(const int&)
第7行:錯誤: va_start在具有固定參數的函數中使用
由於-Wfatal-errors,編譯終止。

尚未提及的一種方法是使用預處理器宏以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預處理程序,但是正因為如此,我不會在此答案中留下__VA_ARGS__ ,這是可以完成的:

#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__)

只要它以int count參數開頭,該宏就可以調用任何可變參數函數。 簡而言之,不要使用:

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

您可以使用:

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

這是使用相同方法的Min的模板版本: https : //gist.github.com/mbolt35/4e60da5aaec94dcd39ca

可變參數函數無法直接確定傳遞了多少個參數。 (至少沒有可移植的方式; <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 幾乎沒有任何理由利用該功能。

如果您在MS Visual Studio下工作,則可以嘗試使用_vscprintf函數。 這是一個如何使用_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);
}

嗯,如果您不擔心討厭的asm hack,那么可以利用編譯器的調用約定。 但是,這會將您的代碼限制為特定的平台/編譯器/調用約定。

例如,在BDS2006 C ++ 32位x86 Windows應用程序 (我將僅指該平台)中,將參數放在堆棧上,然后調用,然后在返回函數后修復堆棧指針值(根據使用的堆棧的大小)。 這里有個小例子:

double x;
x=min(10.0,20.0,30.0,40.0,50.0);

呼叫被轉換為:

Unit1.cpp.28: x=min(10.0,20.0,30.0,40.0,50.0);
00401B9C 6800004940       push $40490000
00401BA1 6A00             push $00
00401BA3 6800004440       push $40440000
00401BA8 6A00             push $00
00401BAA 6800003E40       push $403e0000
00401BAF 6A00             push $00
00401BB1 6800003440       push $40340000
00401BB6 6A00             push $00
00401BB8 6800002440       push $40240000
00401BBD 6A00             push $00
00401BBF E894FDFFFF       call min(double,double,????)
00401BC4 83C428           add esp,$28

注意通話后的最后一條指令。 $28是4個參數和一個返回值消耗的大小。 因此,如果您可以在函數中讀取該值,則可以准確確定參數的數量(如果知道它們的大小)。 所以這里工作的例子:

double min(double x,double ...) // = min(x,y)
        {
        int n,dn=sizeof(double);
        asm {
            mov eax,esp // store original stack pointer 
            mov esp,ebp // get to the parrent scope stack pointer
            pop ebx
            pop ebx     // this reads the return address of the call pointing to the first instruction after it which is what we want
            mov esp,eax // restore stack pointer
            sub eax,eax; // just eax=0
            mov al,[ebx+2] // read lowest BYTE of eax with the $28 from the add esp,$28
            mov n,eax // store result to local variable for usage
            }
        n-=dn;  // remove return value  from the count

        double z; z=x;
        va_list va;
        va_start(va,x); n-=dn;
        for (;n>=0;n-=dn)
            {
            x=va_arg(va,double);
            if (z>x) z=x;
            }
        va_end(va);
        return z;
        }

當心每個編譯器可以有不同的調用順序,因此在使用前先進行調試時要檢查程序集清單!

使用_vscprintf確定變量列表的長度。 https://msdn.microsoft.com/zh-CN/library/w05tbk72.aspx

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM