Reputation: 199
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
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 theva_start
macro, or the function containing the expansion of theva_copy
macro, that initialized theva_list ap
. Theva_end
macro may modifyap
so that it is no longer usable (without being reinitialized by theva_start
orva_copy
macro). If there is no corresponding invocation of theva_start
orva_copy
macro, or if theva_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
, andvsscanf
invoke theva_arg
macro, the value ofarg
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
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.
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.
#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