1

Is it safe and defined behaviour to read va_list like an array instead of using the va_arg function?

EX:

void func(int string_count, ...)
{
    va_start(valist, string_count);
    printf("First argument: %d\n", *((int*)valist));
    printf("Second argument: %d\n", *(((int*)valist)+1));
    va_end(valist);
}

Same question for assigningment EX:

void func(int string_count, ...)
{
    va_start(valist, string_count);
    printf("Third argument: %d\n", *(((int*)valist)+2));
    *((int*)valist+2)=33;
    printf("New third argument: %d\n", *(((int*)valist)+2));
    va_end(valist);
}

PS: This seems to work on GCC

Alex Sim
  • 403
  • 3
  • 16

3 Answers3

3

No, it is not, you cannot assume anything because the implementation varies across libraries. The only portable way to access the values is by using the macros defined in stdarg.h for accessing the ellipsis. The size of the type is important, otherwise you end up reading garage and if your read more bytes than has been passed, you have undefined behaviour.

So, to get a value, you have to use va_arg.

See: STDARG documentation

You cannot relay on a guess as to how va_list works, or on a particular implementation. How va_list works depends on the ABI, the architecture, the compiler, etc. If you want a more in-depth view of va_list, see this answer.

edit

A couple of hours ago I wrote this answer explaining how to use the va_*-macros. Take a look at that.

Pablo
  • 13,271
  • 4
  • 39
  • 59
3

No, this is not safe and well-defined. The va_list structure could be anything (you assume it is a pointer to the first argument), and the arguments may or may not be stored contiguously in the "right order" in some memory area being pointed to.

Example of va_list implementation that doesn't work for your code - in this setup some arguments are passed in registers instead of the stack, but the va_arg still has to find them.

M.M
  • 138,810
  • 21
  • 208
  • 365
  • So what if i do this? `int *firstargument_ptr=&va_arg(valist, int); int alignment = ((int*)&va_arg(valist, int)) - firstargument_ptr` and then i get the subsequent arguments by doing `*(firstargument_ptr+i*alignment)`, would it still be undefined on some compilers? – Alex Sim Feb 26 '18 at 00:54
  • 1
    @AlexSim you **can only use** the macros as explained in the [STDARG documentation](https://linux.die.net/man/3/stdarg). A `va_list` is not a pointer to the first non-named argument, forget about that. – Pablo Feb 26 '18 at 00:58
  • @Pablo In my comment i did not use va_list, i took the address of the first parameter with va_arg and the alignment between two parameters using a second va_arg – Alex Sim Feb 26 '18 at 01:01
  • @AlexSim it's still wrong, because you are assuming things that are simply not true. See https://softwareengineering.stackexchange.com/a/249923 – Pablo Feb 26 '18 at 01:07
  • @Pablo got it, so I can't avoid using dynamic allocation when I have to read it multiple times (ex: sorting elements), can I? – Alex Sim Feb 26 '18 at 02:16
  • @AlexSim dynamic allocation of what? Of the `va_list`? Note that you can only fetch an argument once with `va_arg`. – Pablo Feb 26 '18 at 02:19
  • @Pablo dynamic allocation of an array containing the passed arguments, each member of which is obtained separately using va_arg; is this the only way? – Alex Sim Feb 26 '18 at 02:21
  • @AlexSim you can iterate over the arguments more than once, you can also copy va_lists using va_copy – user253751 Feb 26 '18 at 02:24
  • @immibis last question, can va_copy make the list start from a certain point on (i do a va_args, the a va_copy on a new va_list; will that list start from second parameter?). Also can i edit members of a va_list? – Alex Sim Feb 26 '18 at 02:31
  • @AlexSim `va_copy` copies the list and where it's up to. You can't edit the members – M.M Feb 26 '18 at 02:43
  • @AlexSim I failed to mention that you can iterate over over the arguments multiple times, but you have to do `va_end` and `va_start` again. Yes you can use `va_copy` to create a copy of the `va_list`, see the linked documentation. You cannot change the values returned by `va_arg`, you are only getting a copy of the original. – Pablo Feb 26 '18 at 02:44
  • @AlexSim: If an implementation doesn't expressly document that variadic arguments can be accessed via means other than `va_list`, it might attempt to identify which arguments are or are not used in a given function, and not bother to compute any arguments that are seemingly ignored. The `va_list`-related macros are required to do whatever is necessary to let the compiler know that arguments are used, and are the only standard-defined means of letting compilers know that. – supercat Mar 06 '18 at 18:01
0

If an implementation's documentation specifies that va_list may be used in ways beyond those given in the Standard, you may use them in such fashion on that implementation. Attempting to use arguments in other ways may have unpredictable consequences even on platforms where the layout of parameters is specified. For example, on a platform where variadic arguments are pushed on the stack in reverse order, if one were to do something like:

int test(int x, ...)
{
  if (!x)
    return *(int*)(4+(uintptr_t)&x); // Address of first argument after x
  ... some other code using va_list.
}
int test2(void)
{
  return test(0, someComplicatedComputation);
}

a compiler which is processing test2 might look at the definition of test, notice that it (apparently) ignores its variadic arguments when the first argument is zero, and thus conclude that it doesn't need to compute and pass the result of someComplicatedComputation. Even if the documentation for the platform documents the layout of variadic arguments, the fact that the compiler can't see that they are accessed may cause it to conclude that they are not.

supercat
  • 77,689
  • 9
  • 166
  • 211