C++ 使用变量列表参数时 va_list 的长度?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/2598132/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
length of va_list when using variable list arguments?
提问by Anton Kazennikov
Is there any way to compute length of va_list
? All examples I saw the number of variable parameters is given explicitly.
有没有办法计算长度va_list
?我看到的所有示例都明确给出了可变参数的数量。
采纳答案by Motti
There is no way to compute the length of a va_list
, this is why you need the format string in printf
like functions.
无法计算 a 的长度va_list
,这就是为什么您需要printf
类似函数中的格式字符串。
The only functionsmacros available for working with a va_list
are:
可用于处理 a的唯一函数宏va_list
是:
va_start
- start using theva_list
va_arg
- get next argumentva_end
- stop using theva_list
va_copy
(since C++11 and C99) - copy theva_list
va_start
- 开始使用va_list
va_arg
- 获取下一个参数va_end
- 停止使用va_list
va_copy
(C++11 和 C99 起) - 复制va_list
Please note that you need to call va_start
and va_end
in the same scope which means you can't wrap it in a utility class which calls va_start
in its constructor and va_end
in its destructor (I was bitten by this once).
请注意,您需要在相同的范围内调用va_start
and va_end
,这意味着您不能将其包装在调用va_start
其构造函数和va_end
析构函数的实用程序类中(我曾经被这个咬过)。
For example this class is worthless:
例如这个类是毫无价值的:
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 outputs the following error
GCC 输出以下错误
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.
t.cpp:在构造函数中
arg_list::arg_list(const int&)
:
第 7 行:错误:va_start
在具有固定参数的函数中使用,
由于 -Wfatal-errors 编译终止。
回答by mbolt35
One approach that hasn't been mentioned yet is to use a pre-processor macro to call the variadict function using the va_list length as the first parameter and also forward along the arguments. This is somewhat of a "cute" solution, but does not require manually inputting the argument list length.
尚未提及的一种方法是使用预处理器宏调用 variadict 函数,使用 va_list 长度作为第一个参数,并沿参数转发。这有点“可爱”的解决方案,但不需要手动输入参数列表长度。
Assume you have the following function:
假设您有以下功能:
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;
}
The idea is that you have a preprocessor macro capable of counting the number of arguments by using a mask for the __VA_ARGS__
. There are a few good preprocessor libraries for determining the __VA_ARGS__
length including P99and Boost Preprocessor, but just so I don't leave holes in this answer, here's how it can be done:
这个想法是你有一个预处理器宏,能够通过使用掩码来计算参数的数量__VA_ARGS__
。有一些很好的预处理器库可用于确定__VA_ARGS__
长度,包括P99和 Boost Preprocessor,但我不会在这个答案中留下漏洞,这是如何完成的:
#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
Note: A lot of the noise from above is work-around support for MSVC.
注意:上面的许多噪音是对 MSVC 的变通支持。
With the above defined, you can create a single macro to perform all length based operations:
通过上面的定义,您可以创建一个宏来执行所有基于长度的操作:
/**
* 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__)
This macro is capable of calling any variadict function as long as it begins with the int count
parameter. In short, instead of using:
只要它以int count
参数开头,这个宏就能够调用任何可变参数函数。简而言之,而不是使用:
int result = Min(5, 1, 2, 3, 4, 5);
You can use:
您可以使用:
int result = ExecVF(Min, 1, 2, 3, 4, 5);
Here's a template version of Min which uses the same approach: https://gist.github.com/mbolt35/4e60da5aaec94dcd39ca
这是使用相同方法的 Min 的模板版本:https: //gist.github.com/mbolt35/4e60da5aaec94dcd39ca
回答by Keith Thompson
There is no directway for a variadic function to determine how many arguments were passed. (At least there's no portable way; the <stdarg.h>
interface doesn't provide that information.)
可变参数函数没有直接的方法来确定传递了多少参数。(至少没有可移植的方式;<stdarg.h>
界面不提供该信息。)
There are several indirectways.
有几种间接方式。
Two the most common are:
两个最常见的是:
- A format string (which specifies, via what you might call a small simple language, the number and type(s) of the remaining arguments). The
*printf()
and*scanf()
families of functions use this mechanism. - A sentinel value denoting the end of the arguments. Some of the Unix/POSIX
exec*()
family of functions do this, using a null pointer to mark the end of the arguments.
- 格式字符串(通过您可能称之为小型简单语言的方式指定剩余参数的数量和类型)。该
*printf()
和*scanf()
功能的家庭使用了这种机制。 - 一个标记值,表示参数的结尾。一些 Unix/POSIX
exec*()
函数族这样做,使用空指针来标记参数的结尾。
But there are other possibilities:
但还有其他可能性:
- More simply, a integer count that specifies the number of following arguments; presumably in this case they'd all be of the same type.
- Alternating arguments, where an argument can be an enumeration value specifying the type of the following argument. A hypothetical example might look like:
func(ARG_INT, 42, ARG_STRING, "foo", ARG_DOUBLE, 1.25, ARG_END);
or even:func("-i", 42, "-s", "foo", "-d", 1.25, "");
if you want to emulate the way arguments are typically passed to Unix commands.
- 更简单地说,一个整数计数,指定以下参数的数量;大概在这种情况下,它们都属于同一类型。
- 交替参数,其中参数可以是指定以下参数类型的枚举值。一个假设的例子可能看起来像:
func(ARG_INT, 42, ARG_STRING, "foo", ARG_DOUBLE, 1.25, ARG_END);
或者甚至:func("-i", 42, "-s", "foo", "-d", 1.25, "");
如果你想模拟参数通常传递给 Unix 命令的方式。
You could even assign a value to a global variable to specify the number of arguments:
您甚至可以为全局变量赋值以指定参数的数量:
func_arg_count = 3;
func(1, 2, 3);
which would be ugly but perfectly legal.
这将是丑陋但完全合法的。
In all these techniques, it's entirely the caller's responsibility to pass consistent arguments; the callee can only assumethat its parameters are correct.
在所有这些技术中,传递一致的参数完全是调用者的责任;被调用者只能假设它的参数是正确的。
Note that a variadic function is not required to process all the arguments passed to it. For example, this:
请注意,可变参数函数不需要处理传递给它的所有参数。例如,这个:
printf("%d\n", 10, 20);
will print 10
and quietly ignore the 20
. There's rarely any reason to take advantage of that feature.
将打印10
并悄悄忽略20
. 很少有理由利用该功能。
回答by Ruza
You can try to use function _vscprintf
if you work under MS Visual Studio.
Here is an example how to use _vscprintf, I used it to know how much space I require to malloc for my console title.
_vscprintf
如果您在 MS Visual Studio 下工作,可以尝试使用函数。这是一个如何使用 _vscprintf 的示例,我用它来了解我需要为控制台标题分配多少空间。
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;
}
Or you can do this, add output to temporary file and then read data from it to allocated memory like I did in this next example:
或者您可以这样做,将输出添加到临时文件,然后从它读取数据到分配的内存,就像我在下一个示例中所做的那样:
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);
}
回答by Spektre
Hmm if you are not afraid of nasty asm hack then you can exploit the calling convention of your compiler. However this will limit your code to specific platform/compiler/calling convention.
嗯,如果您不害怕讨厌的 asm hack,那么您可以利用编译器的调用约定。但是,这会将您的代码限制为特定的平台/编译器/调用约定。
For example in BDS2006 C++ 32bit x86 Windows app(I will refer to this platform only) the arguments are put onto stack then called and then the stack pointer value is repaired (by the size of used stack) after function is returned. Here small example:
例如,在BDS2006 C++ 32 位 x86 Windows 应用程序中(我将仅参考该平台),将参数放入堆栈然后调用,然后在函数返回后修复堆栈指针值(通过使用堆栈的大小)。这里的小例子:
double x;
x=min(10.0,20.0,30.0,40.0,50.0);
the call is translated to this:
电话被翻译成这样:
Unit1.cpp.28: x=min(10.0,20.0,30.0,40.0,50.0);
00401B9C 6800004940 push 490000
00401BA1 6A00 push 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 from the add esp,
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;
}
00401BA3 6800004440 push 440000
00401BA8 6A00 push ##代码##
00401BAA 6800003E40 push 3e0000
00401BAF 6A00 push ##代码##
00401BB1 6800003440 push 340000
00401BB6 6A00 push ##代码##
00401BB8 6800002440 push 240000
00401BBD 6A00 push ##代码##
00401BBF E894FDFFFF call min(double,double,????)
00401BC4 83C428 add esp,
pay attention to the last instruction after the call. the $28
is the size consumed by 4 arguments and one return value. So if you can read that value in your function you can determine exactly the number of arguments (if their size is known). So here working example:
注意调用后的最后一条指令。的$28
是4个参数和返回值消耗的大小。因此,如果您可以在函数中读取该值,则可以准确确定参数的数量(如果它们的大小已知)。所以这里的工作示例:
Beware each compiler can have different calling sequence so first check in assembly listing while debug before use !!!
注意每个编译器可能有不同的调用顺序,所以在使用前先在调试时检查汇编列表!!!
回答by Yashwant Patil
Use _vscprintf to determine length of variable list. https://msdn.microsoft.com/en-us/library/w05tbk72.aspx
使用 _vscprintf 确定变量列表的长度。 https://msdn.microsoft.com/en-us/library/w05tbk72.aspx