Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

va_arg – different behavior on Linux and Windows

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 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:

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

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?

>Solution :

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, and va_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 type
va_list. 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 and shall be
passed to the va_end macro prior to any further reference to ap
.253)

  1. 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);
}
Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading