0

I wrote a function that accepts a va_list, and that is meant to be invoked iteratively by its caller. It should modify the va_list and changes should persist back in the caller so that the next call to the function will proceed with the next argument.

I can't post that code specifically, but here's a snippet that reproduces the situation (godbolt link):

#include <stdarg.h>
#include <stdio.h>

void print_integer(va_list ap) {
    printf("%i\n", va_arg(ap, int));
}

void vprint_integers(int count, va_list ap) {
    for (int i = 0; i < count; ++i) {
        print_integer(ap);
    }
}

void print_integers(int count, ...) {
    va_list ap;
    va_start(ap, count);
    vprint_integers(count, ap);
    va_end(ap);
}

int main() {
    print_integers(3, 1, 2, 3);
}

This works (prints "1 2 3") on my x86 platform because va_list is passed by "reference" (it's probably declared as an array of one element, so va_list arguments decay to a pointer). However, it does not work on my ARM platform (prints "1 1 1"), where va_list seems to be defined as a pointer to something. On that platform, va_arg always returns the first argument.

The next best option seems to be to make ap a pointer:

#include <stdarg.h>
#include <stdio.h>

void print_integer(va_list *ap) {
    printf("%i\n", va_arg(*ap, int));
}

void vprint_integers(int count, va_list ap) {
    for (int i = 0; i < count; ++i) {
        print_integer(&ap);
    }
}

void print_integers(int count, ...) {
    va_list ap;
    va_start(ap, count);
    vprint_integers(count, ap);
    va_end(ap);
}

int main() {
    print_integers(3, 1, 2, 3);
}

This works on ARM (with va_arg(*ap, ...)), but it does not compile on x86. When I try print_integer(&ap) on x86, Clang says:

error: incompatible pointer types passing 'struct __va_list_tag **' to parameter of type 'va_list *' (aka '__builtin_va_list *')

This only seems to happen when taking the address of a va_list passed as an argument, not when it's taken from a local variable. Unfortunately, I do need my v variant to take a va_list object and not a pointer to it.

It's easy to get consistent cross-platform value semantics for va_list using va_copy. Is there a cross-platform way to get consistent reference semantics for va_list?

zneak
  • 134,922
  • 42
  • 253
  • 328
  • Can we see `va_do_stuff`? How you're using `va_list` is probably important. – Schwern Apr 01 '20 at 20:31
  • @Schwern, you unfortunately can't. [Here's an example](https://godbolt.org/z/dTEoaC) if this helps you understand what I'm talking about: it prints "1 2 3" on x86 and "1 1 1" on ARM, and my goal is to pass the va_list in a way that it prints "1 2 3" on ARM too. Aside from that, I don't know what `va_do_stuff` could help you more with to answer this question. – zneak Apr 01 '20 at 20:37
  • @zneak That should be included in the question, as well as the version using `va_list *ap` – dbush Apr 01 '20 at 20:40
  • @zneak We don't need to see the original, just a [MCVE](https://stackoverflow.com/help/minimal-reproducible-example) which demonstrates the problem. Your example helps, but we also need to see how you're modifying `va_list`. – Schwern Apr 01 '20 at 20:49
  • The question has been updated. – zneak Apr 01 '20 at 20:50
  • 1
    Similar: https://stackoverflow.com/questions/8047362/is-gcc-mishandling-a-pointer-to-a-va-list-passed-to-a-function – M.M Apr 02 '20 at 00:02

3 Answers3

1

The thing at issue here is that va_list, on the x86 platform, is defined as an array of 1 element (let's call it __va_list_tag[1]). It decays to a pointer when accepted as an argument, so &ap is wildly different depending on whether ap is a parameter of the function (__va_list_tag**) or a local variable (__va_list_tag(*)[1]).

One solution that works for this case is simply to create a local va_list, use va_copy to populate it, and pass a pointer to this local va_list. (godbolt)

void vprint_integers(int count, va_list ap) {
    va_list local;
    va_copy(local, ap);
    for (int i = 0; i < count; ++i) {
        print_integer(&local);
    }
    va_end(local);
}

In my case, vprint_integers and va_copy are necessary because the interface of vprint_integers accepts a va_list and that cannot change. With more flexible requirements, changing vprint_integers to accept a va_list pointer is fine too.

va_list isn't specified to be anything in particular, but it's defined to be an object type, so there's not really a reason to believe that you can't take or pass its address. Another very similar solution that entirely bypasses the question of whether you can take the address of a va_list is to wrap the va_list in a struct and pass a pointer to that struct.

zneak
  • 134,922
  • 42
  • 253
  • 328
0

You may be out of luck. 7.15(3) says what you're doing has indeterminate effects.

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.

The call to va_arg in print_integer is causing ap in print_integers to be indeterminate. On one architecture it's being incremented, on another it isn't.

As for what va_list is, it could be anything...

...which is an object type suitable for holding information needed by the macros va_start, va_arg, va_end, and va_copy.

You may wish to consider a different approach to solving the underlying problem.

Schwern
  • 153,029
  • 25
  • 195
  • 336
  • One thing I don't like about x-y problems is that there seems to be an assumption that OP doesn't actually know what their problem is. Asking for a minimum repro case is an excellent step that I did not spend enough time on. On the other hand, I believe that for most people who are writing code professionally, telling other people "you can't do that" is usually perceived as more respectful of their abilities than telling them "I could help *if only you told me more about your requirements*". My requirements are old enough to drink. – zneak Apr 01 '20 at 23:31
  • `ap` in `print_integers` is not used after being made indeterminate, so there is no problem in that regard. It is a common pattern to pass `va_list` "by value" (as such) to a function, and of course not continue to use it in the calling context. – M.M Apr 02 '20 at 00:00
  • @zneak A professional does not take it personally when asked to consider an oblique solution to a hard problem. If it's not an XY Problem they say "this is not an XY Problem", or they say nothing at all. I hope you solve your problem. – Schwern Apr 02 '20 at 02:20
  • @Schwern, if my comment rubbed you the wrong way, consider asking yourself why you feel that I am not justified to criticize your communications whereas you find yourself justified to criticize mine. – zneak Apr 02 '20 at 02:36
0

The problem in the second program is that if va_list is an array type, then the function parameter ap has its type adjusted to be a pointer type. So the argument has a different level of indirection in each case.

Since C11 we can solve this with a generic selector to test whether or not ap is still a va_list:

void vprint_integers(int count, va_list ap) {
    for (int i = 0; i < count; ++i) {
        print_integer((va_list *)_Generic(&ap, va_list*: &ap, default: ap));
    }

This solution was inspired by this answer which used a macro for the two cases that required manual configuration .

Notes about the _Generic:

  • We have to test &ap because, since C18, array-to-pointer decay is performed on the first expression.
  • Originally I had _Generic(&ap, va_list*: &ap, default: (va_list *)ap) however this is rejected by compilers which check for constraint violations in all branches for all inputs -- although do not go on to check constraint violations in the surrounding expressions for unselected branches.
M.M
  • 138,810
  • 21
  • 208
  • 365
  • This doesn't build on some x86 platforms (notably mine and the one that runs [godbolt](https://godbolt.org/z/EqVXXe)). It's the first thing that I tried. – zneak Apr 01 '20 at 23:50
  • @zneak oh, the problem is that function parameter `ap` no longer has the type `va_list` due to adjustment. I'll revise my answer – M.M Apr 01 '20 at 23:54
  • I posted what you're probably about to post a few minutes ago. :) – zneak Apr 01 '20 at 23:54
  • 1
    @zneak I had something else in mind, there is more than one way to skin a cat :) – M.M Apr 01 '20 at 23:59
  • @zneak my answer is now either genius or horrible, can you test it on your platform? – M.M Apr 02 '20 at 00:31
  • This would work for all the ABIs that Clang supports, I believe. On the other hand, I do like the certainty of where reference semantics start and end with the va_copy approach. – zneak Apr 02 '20 at 00:59
  • @zneak OK. I managed to fix the original problem in my answer with constraint violation – M.M Apr 02 '20 at 01:25