length of va_list when using variable list arguments?
Is there any way to compute开发者_JAVA技巧 length of va_list
? All examples I saw the number of variable parameters is given explicitly.
There is no way to compute the length of a va_list
, this is why you need the format string in printf
like functions.
The only functions macros available for working with a va_list
are:
va_start
- start using theva_list
va_arg
- get the next argumentva_end
- stop using theva_list
va_copy
(since C++11 and C99) - copy theva_list
Please note that you need to call va_start
and va_end
in the same scope which means you cannot 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).
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
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.
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.
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 P99 and Boost Preprocessor, but just so I don't leave holes in this answer, here's how it can be done:
#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.
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 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
There is no direct way 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.)
There are several indirect ways.
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.
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.
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 assume that 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.
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.
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:
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 $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
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:
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;
}
Beware each compiler can have different calling sequence so first check in assembly listing while debug before use !!!
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.
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);
}
Use _vscprintf to determine length of variable list. https://msdn.microsoft.com/en-us/library/w05tbk72.aspx
精彩评论