开发者

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 the va_list
  • va_arg - get the next argument
  • va_end - stop using the va_list
  • va_copy (since C++11 and C99) - copy the va_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

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜