Reputation: 53
I run this in MSVC v142.
I need to keep multiple va_list
and then pass all of them in an array to another API.
However, the old va_list
were getting overwritten by the newer one.
void toVaList(va_list *out, int count, ...)
{
va_list args;
va_start(args, count);
vprintf("toVaList: %d %d\n", args);
*out = args;
}
int main()
{
va_list args1;
toVaList(&args1, 2, 11, 22);
vprintf("first va_list: %d %d\n", args1);
va_list args2;
toVaList(&args2, 2, 33, 44);
vprintf("first va_list: %d %d\n", args1); //now args1 are overwritten
vprintf("second va_list: %d %d\n", args2);
va_end(args1);
va_end(args2);
return 0;
}
The output is
toVaList: 11 22
first va_list: 11 22
toVaList: 33 44
first va_list: 33 44
second va_list: 33 44
Is it possible to resolve this issue?
Edit:
Now memcpy
works for me. I copied the va_list
to a separate memory and kept the original va_list
in the function body wrapped by va_start
and va_end
. It looks like the copied va_list
outside of the function body works well.
vprintf
doesn't invalidate the va_list
args, since it might have made a va_copy
inside its implementation.
void toVaList1(va_list *out, int count, ...)
{
va_list args;
va_start(args, count);
memcpy(out, args, 256);
vprintf("toVaList1: %d %d\n", args);
va_end(args);
}
int main()
{
char args1[256];
toVaList1((va_list *)args1, 2, 11, 22);
vprintf("first va_list: %d %d\n", args1);
char args2[256];
toVaList1((va_list *)args2, 2, 33, 44);
vprintf("first va_list: %d %d\n", args1);
vprintf("second va_list: %d %d\n", args2);
char args3[256];
toVaList1((va_list *)args3, 2, 55, 66);
vprintf("first va_list: %d %d\n", args1);
vprintf("second va_list: %d %d\n", args2);
vprintf("third va_list: %d %d\n", args3);
}
The output is
toVaList1: 11 22
first va_list: 11 22
toVaList1: 33 44
first va_list: 11 22
second va_list: 33 44
toVaList1: 55 66
first va_list: 11 22
second va_list: 33 44
third va_list: 55 66
Edit:
Briefly describe the scenario for this requirement. We have a list of messages with id defined in XML, and an internal API that takes msgId and va_list as arguments, which uses va_arg() to read va_list:
ID Message
1 This is a message containing integer %d and string %s.
2 This is another message containing double %f.
.....
void internalAPI_PrintMsg(int msgId, va_list ap)
{
char resultMsg[512];
//lookup message id in XML and substitute arguments
//in the loop through found message body...
switch(char)
{
case 'd':
int i = va_arg(ap, int);
//substitute the integer
break;
case 'f':
double d = va_arg(ap, double);
//substitute the double
break;
....
}
PrintResultMessage(resultMsg);
}
We already have API to print single message:
void printMsg(int msgId, ...)
{
va_list args;
va_start(args, msgId);
internalAPI_PrintMsg(msgId, args);
va_end(args);
}
Now we need to support an API to print a group of messages:
typedef struct
{
long id;
char[256] args;
} msgData;
void printGroupMsg(msgData * msgArr, int msgCount) //this is the new API that requires multiple va_list
{
//do some other stuff to start group messaging
for (int i = 0; i < msgCount; ++i)
internalAPI_PrintMsg(msgArr[i].id, msgArr[i].args);
//do some other stuff to end group messaging
}
void initMsgData(msgData *msg, int msgId, ...) //this is equivalent to toVaList(...)
{
msg->id = msgId;
va_list args;
va_start(args, msgId);
memcpy(msg->args, args, 256);
va_end(args);
}
//client code:
msgData *arr = malloc(sizeof(msgData)*2);
initMsgData(arr, 1, 88, "test");
initMsgData(arr+1, 2, 123.456);
printGroupMsg(arr, 2);
Upvotes: 2
Views: 383
Reputation: 144780
va_copy
must be used in place of *out = args;
to makes a copy of the va_list
but it does not change the scope where it is valid to enumerate it, which is the called function. Referring to the va_list
after the function toVaList
returns has undefined behavior.
This restriction is not specified explicitly in the C Standard. The relevent language is this:
7.16.1 Variable argument list access macros
...
Each invocation of theva_start
andva_copy
macros shall be matched by a corresponding invocation of theva_end
macro in the same function.
This phrase means that va_end()
must be called in the same function that called va_start
or va_copy
for the same va_list
argument, hence this argument cannot be used once the function returns.
Your approach has undefined behavior per the C Standard. This is easy to understand from an implementation standpoint: calling a function with a variable number of arguments involves passing these arguments to the function in an implementation defined way that the variable argument accessing macros abstracts. Yet like any other function call, the arguments go out of scope when the function returns to its caller. If you want to access them at some later point, you must enumerate them and save their values to an array or structure with an appropriate scope and life time. The memcpy
hack attempts to do this, but its behavior is undefined.
Here is an alternative approach:
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
int **array_from_args(int count, ...) {
va_list ap;
va_start(ap, count);
int *a = malloc(sizeof(*a) * (1 + count));
if (a) {
a[0] = count;
for (int i = 0; i < count; i++) {
a[i + 1] = va_arg(ap, int);
}
}
va_end(ap);
return a;
}
void print_array(const char *s, const int *a) {
printf("%s:", s);
if (a) {
for (int i = 0, n = a[0]; i < n; i++)
printf(" %d", a[i + 1]);
}
printf("\n");
}
int main() {
int *args1 = array_from_args(2, 11, 22);
int *args2 = array_from_args(2, 33, 44);
int *args3 = array_from_args(2, 55, 66);
print_array("args1", args1);
print_array("args2", args2);
print_array("args3", args3);
free(args1);
free(args2);
free(args3);
}
You could generalize this approach for arguments of different types by providing type information as an argument to the array_from_args()
, like a printf
format string, and construct an array of tagged structures.
Upvotes: 1
Reputation: 754060
Note that the C standard stipulates that if you invoke va_start
in function, you must also invoke va_end
in the same function:
§7.16.1 Variable argument list access macros
¶1 The
va_start
andva_arg
macros described in this subclause shall be implemented as macros, not functions. It is unspecified whetherva_copy
andva_end
are macros or identifiers declared with external linkage. If a macro definition is suppressed in order to access an actual function, or a program defines an external identifier with the same name, the behavior is undefined. Each invocation of theva_start
andva_copy
macros shall be matched by a corresponding invocation of theva_end
macro in the same function.
[…Emphasis added…]
Your toVaList
function is violating that requirement and therefore invokes undefined behaviour.
The preamble to §7.16 says:
3 The type declared is
va_list
which is a complete object type suitable for holding information needed by the macros
va_start
,va_arg
,va_end
, andva_copy
. If access to the varying arguments is desired, the called function shall declare an object (generally referred to asap
in this subclause) having typeva_list
. The objectap
may be passed as an argument to another function; if that function invokes theva_arg
macro with parameterap
, the value ofap
in the calling function is indeterminate and shall be passed to theva_end
macro prior to any further reference toap
.253)253) It is permitted to create a pointer to a
va_list
and pass that pointer to another function, in which case the original function may make further use of the original list after the other function returns.
Upvotes: 4