I did some test code to demonstrate different output on Windows and Linux
#include <stdarg.h>
#include <stdio.h>
void print_vint(va_list args)
{
int i = va_arg(args, int);
printf("%d\n", i);
}
void variadic_f(const char* format, ...)
{
va_list args;
va_start(args, format);
print_vint(args);
print_vint(args);
va_end(args);
}
int main(int argc, char* argv[])
{
variadic_f("dummy ", 1, 2);
return 0;
}
print_vint
function has argument of type va_list
passed by value. On a Windows machine it is pointer to stack right after the last nonvariadic parameter. Parameter is passed to function by value and function changes it when invoking va_arg
. Second invocation of print_vint
gets same pointer value (since local copy inside function was changed) and prints same output as first invocation.
Here is Windows output of program invocation:
1
1
I compile same source code on Linux and run. Output is different:
1
2
My conclusion is that Linux uses pointer to opaque structure which has pointer to stack and when va_list
is passed by value dereferencing pointer and changing pointer inside structure will be visible in consecutive use of va_list
.
When I change print_vint
to accept pointer, it works same way on Linux and Windows.
void print_vint(va_list *args)
{
int i = va_arg(*args, int);
printf("%d\n", i);
}
Does the C standard specify how va_arg
should behave and how execution affects va_list
argument?
The error here is that you passed a va_list
into another function and called va_arg
in that function. Once you do that, it's not permitted to use the va_list
again in the caller.
This is spelled out in section 7.16p3 of the C standard regarding variable arguments:
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 as ap 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)
- 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.
So your code triggers undefined behavior by accessing args
after its value becomes indeterminate.
Note that footnote 253 above states that you can pass a pointer to a va_list
to do what you want:
void print_vint(va_list *args)
{
int i = va_arg(*args, int);
printf("%d\n", i);
}
void variadic_f(const char* format, ...)
{
va_list args;
va_start(args, format);
print_vint(&args);
print_vint(&args);
va_end(args);
}