Jcppython
Jcppython

Reputation: 199

Can I use `va_list` twice as following:

Can I use va_list as following:

void myself_printf(char* key, char* value, ...) {
    char real_key[1024];
    char real_value[1024];
    va_list args;
    va_start(args, value);

    // use args twice format key and value
    vsnprintf(real_key, 1024000, key, args);
    vsnprintf(real_value, 1024000, value, args);

    va_end(args);
}

using in demo

myself_printf("%d-%s", "%s-%d", 12, "key", "value", 24);
expect: real_key is "12-key", and real_value is "value-24"

Upvotes: 8

Views: 2750

Answers (2)

Jonathan Leffler
Jonathan Leffler

Reputation: 754620

As an alternative to using va_copy() — as suggested by Dietrich Epp in an answer — you can simply use va_start() and va_end() twice.

void myself_printf(char *key_fmt, char *value_fmt, ...)
{
    char real_key[1024];
    char real_value[1024];
    va_list args;

    va_start(args, value);
    vsnprintf(real_key, sizeof(real_key), key_fmt, args);
    va_end(args);

    vs_start(args, value);
    vsnprintf(real_value, sizeof(real_key), value_fmt, args);
    va_end(args);

    …do something useful with real_key and real_value…
}

The original version of the question used char real_key[1024000]; and similarly for real_value. Allocating almost 2 MiB of data on the stack won't work reliably on Windows (the limit there is usually 1 MiB of stack), and uses an awful lot of space on Unix systems (where the stack size is usually 8 MiB). Be cautious!

You would need to use va_copy() if the va_list was passed as an argument to your function. For example:

void myself_printf(char* key, char* value, ...)
{
    va_list args;    
    va_start(args, value);
    myself_vprintf(key, value, args);
    va_end(args);
}

void myself_vprintf(char *key_fmt, char *value_fmt, va_list args1)
{
    char real_key[1024];
    char real_value[1024];
    va_list args2;
    va_copy(args2, args1);

    vsnprintf(real_key, sizeof(real_key), key_fmt, args1);
    vsnprintf(real_value, sizeof(real_value), value_fmt, args2);
    va_end(args2);

    …do something useful with real_key and real_value…
}

The specification of va_end() says:

The va_end macro facilitates a normal return from the function whose variable argument list was referred to by the expansion of the va_start macro, or the function containing the expansion of the va_copy macro, that initialized the va_list ap. The va_end macro may modify ap so that it is no longer usable (without being reinitialized by the va_start or va_copy macro). If there is no corresponding invocation of the va_start or va_copy macro, or if the va_end macro is not invoked before the return, the behavior is undefined.

Note that the specification of the vfprintf() function includes footnote 288 which says:

As the functions vfprintf, vfscanf, vprintf, vscanf, vsnprintf, vsprintf, and vsscanf invoke the va_arg macro, the value of arg after the return is indeterminate.

Granted, footnotes are not normative, but this is a strong indication that the functions are expected to use va_arg and hence the double use of arg as shown in the question leads to undefined behaviour unless there is an intervening call to va_end and another va_start or a use of va_copy (and its matching va_end).

Upvotes: 5

Dietrich Epp
Dietrich Epp

Reputation: 213588

Strictly speaking, you must use va_copy, since vsnprintf can invalidate args.

void myself_printf(char* key, char* value, ...) {
    char real_key[1024000];
    char real_value[1024000];
    va_list args, args2;
    va_start(args, value);
    va_copy(args2, args);

    // use args twice format key and value
    vsnprintf(real_key, 1024000, key, args);
    vsnprintf(real_value, 1024000, value, args2);

    va_end(args);
    va_end(args2);
}

This is what va_copy is designed for.

As noted, this is a large amount of stack space, although it is within typical stack sizes. Consider using vasprintf, if it is available.

Citations

n1548 §7.16

The object ap may be passed as an argument to another function; if that function invokes the va_arg macro with parameter ap, the value of ap in the calling function is indeterminate…

In other words, you cannot portably use args after you pass it to vsnprintf.

This is clarified in footnote 281:

As the functions vfprintf, vfscanf, vprintf, vscanf, vsnprintf, vsprintf, and vsscanf invoke the va_arg macro, the value of arg after the return is indeterminate.

While it seems that va_list is passed by value, that does not mean that it is actually passed by value, or that the va_list itself encapsulates all of its state. A common trick with typedefs in C is to declare them as a 1-element array:

typedef int my_type[1];

Since my_type decays to pointer type when it is passed by function, it only appears to be passed by value.

Demo

#include <stdio.h>
#include <stdarg.h>
void func(const char *msg, ...) {
    va_list ap;
    va_start(ap, msg);
    vfprintf(stdout, msg, ap);
    vfprintf(stdout, msg, ap);
    va_end(ap);
}
int main(int argc, char **argv) {
    func("%d + %d = %d\n", 2, 3, 5);
    return 0;
}

On my computer, the output is:

2 + 3 = 5
590862432 + -1635853408 = 1586038440

Upvotes: 12

Related Questions