有没有办法计算va_list
的长度?我看到的所有示例都明确给出了变量参数的数量。
答案 0 :(得分:29)
无法计算va_list
的长度,这就是您需要printf
函数中的格式字符串的原因。
唯一可用于处理va_list
are的函数宏:
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
1}}在它的析构函数中(我被这一次咬过)。
例如,这个课程毫无价值:
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:在构造函数
arg_list::arg_list(const int&)
中:
第7行:错误:va_start
用于具有固定args的函数中 由于-Wfatal-errors导致编译终止。
答案 1 :(得分:11)
尚未提及的一种方法是使用预处理器宏来调用variadict函数,使用va_list length作为第一个参数,并沿着参数转发。这有点像“可爱”的解决方案,但不需要手动输入参数列表长度。
假设您具有以下功能:
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__)
只要它以int count
参数开头,该宏就能调用任何variadict函数。简而言之,而不是使用:
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
答案 2 :(得分:10)
对于可变参数函数,没有直接方法来确定传递了多少个参数。 (至少没有可移植的方式; <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
。很少有理由利用这个功能。
答案 3 :(得分:2)
如果您在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);
}
答案 4 :(得分:2)
嗯,如果你不害怕讨厌的asm hack那么你可以利用编译器的调用约定。但是,这会将您的代码限制为特定的平台/编译器/调用约定。
例如在 BDS2006 C ++ 32位x86 Windows应用程序(我将仅参考此平台)中,将参数放入堆栈然后调用,然后修复堆栈指针值(按使用的大小) stack)返回函数后。这里有一个小例子:
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;
}
请注意每个编译器可以有不同的调用顺序,因此在使用前先调试汇编列表!
答案 5 :(得分:-2)
使用_vscprintf确定变量列表的长度。 https://msdn.microsoft.com/en-us/library/w05tbk72.aspx