[英]length of va_list when using variable list arguments?
有什么方法可以計算va_list
長度嗎? 我看到的所有示例都明確給出了可變參數的數量。
無法計算va_list
的長度,這就是為什么在像printf
這樣的函數中需要格式字符串的原因。
va_start
開始使用va_list
va_arg
獲取下一個參數 va_end
停止使用va_list
va_copy
(自C ++ 11和C99起)-復制va_list
請注意,您需要在相同的作用域中調用va_start
和va_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()
系列函數使用此機制。 exec*()
函數系列使用一個空指針來標記參數的結尾來執行此操作。 但是還有其他可能性:
func(ARG_INT, 42, ARG_STRING, "foo", ARG_DOUBLE, 1.25, ARG_END);
func("-i", 42, "-s", "foo", "-d", 1.25, "");
您甚至可以為全局變量分配一個值,以指定參數數量:
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.