Passing va_list by reference to a function

Discussion in 'C Programming' started by Urs Thuermann, Nov 22, 2010.

  1. How can I pass a va_list to another function by reference? In a
    simple libc implementation I have some functions as follows:

    static int xprintf(struct callback *cb, const char *fmt, va_list ap)
    {
    ...
    switch (conv_spec) {
    case 'u':
    u = get_uint(conv_mods, &ap);
    ...
    break;
    case 'x':
    u = get_uint(conv_mods, &ap);
    ...
    case 'd':
    n = get_int(conv_mods, &ap);
    ...
    ...
    }
    }

    static unsigned long long get_uint(int mods, va_list *ap)
    {
    unsigned long long u;

    switch (mods) {
    case MOD_NONE:
    case MOD_H:
    case MOD_HH:
    u = va_arg(*ap, unsigned int);
    break;
    case MOD_L:
    u = va_arg(*ap, unsigned long int);
    break;
    case MOD_L:
    u = va_arg(*ap, unsigned long long int);
    break;
    case MOD_Z:
    u = va_arg(*ap, size_t);
    break;
    }
    return u;
    }

    On my x86 32-bit system this compiles and works as intended. va_list
    is simply a pointer. When I ported to x86_64 this produces compiler
    warnings. On that system va_list is an array containing 1 element of
    a struct type. Therefore, &ap has type struct __va_list_tag ** while
    get_uint() expects an va_list* which is struct __va_list_tag (*)[1].

    As a quick work-around I have put the va_list into a struct of which I
    pass the address into get_uint(). This works on both systems and
    seems to be portable but looks really ugly:

    static int xprintf(struct callback *cb, const char *fmt, va_list ap)
    {
    va_struct aps;
    va_copy(aps.ap, ap);

    ...;
    switch (conv_spec) {
    case 'u':
    u = get_uint(conv_mods, &aps);
    ...;
    }
    va_end(aps.ap);
    }

    static unsigned long long get_uint(int mods, struct va_struct *aps)
    {
    ...;
    u = va_arg(aps->ap, unsigned int);
    ...;
    }

    Isn't there a cleaner way to portably pass a va_list to a function by
    reference?

    Also, what is the reason to typedef va_list as an array containing one
    struct instead of just doing a typedef struct __va_list_tag va_list,
    in which case no problem would occur with my original code? I think I
    have read somewhere about this implementation as array but I don't
    remember where and cannot find that document again.

    urs
    Urs Thuermann, Nov 22, 2010
    #1
    1. Advertising

  2. pete <> writes:

    > There's no obvious reason for the ap parameter
    > to be a (va_list *) type instead of a va_list type.
    >
    > Why isn't that written this way instead?:
    > static unsigned long long get_uint(int mods, va_list ap)
    > {
    > unsigned long long u;
    >
    > switch (mods) {
    > case MOD_NONE:
    > case MOD_H:
    > case MOD_HH:
    > u = va_arg(ap, unsigned int);
    > break;
    > case MOD_L:
    > u = va_arg(ap, unsigned long int);
    > break;
    > case MOD_L:
    > u = va_arg(ap, unsigned long long int);
    > break;
    > case MOD_Z:
    > u = va_arg(ap, size_t);
    > break;
    > }
    > return u;
    > }


    Then the function get_uint() might change only a *copy* of the value
    of ap, but not the value of ap from xprintf(). Successive calls of
    get_uint() would then always return the same int. This happens if
    va_list is not an array type but a pointer or a struct type, as it is
    e.g. for gcc for 32 bit x86.

    BTW, this is the reason I strongly dislike typedef's for array types.
    The typedef hides the fact that this type-name defines an array which
    gives surprises when passing as argument or when assigning to a
    variable of that type.

    What is the reason for x86_64 gcc's, and probably other
    implementations's

    typedef struct __va_list_tag va_list[1];

    instead of simply

    typedef struct __va_list_tag va_list;

    which I would prefer, since it behaves more similar to

    typedef void *va_list;

    as is done in many other implementations? Is is only to reduce
    overhead when passing an va_list, e.g. from printf() to vprintf()?
    Hm, I now see that this is indeed a valid reason. But it means that
    va_list is sometimes passed by copying the value and sometimes by only
    passing a pointer (which is obvioulsy intended to save overhead). But
    then I also don't see a clean solution to my problem.

    urs
    Urs Thuermann, Nov 22, 2010
    #2
    1. Advertising

  3. Urs Thuermann

    Tim Rentsch Guest

    Urs Thuermann <> writes:

    > How can I pass a va_list to another function by reference? In a
    > simple libc implementation I have some functions as follows:
    >
    > static int xprintf(struct callback *cb, const char *fmt, va_list ap)
    > {
    > ...
    > switch (conv_spec) {
    > case 'u':
    > u = get_uint(conv_mods, &ap);
    > ...
    > break;
    > case 'x':
    > u = get_uint(conv_mods, &ap);
    > ...
    > case 'd':
    > n = get_int(conv_mods, &ap);
    > ...
    > ...
    > }
    > }
    >
    > static unsigned long long get_uint(int mods, va_list *ap)
    > {
    > unsigned long long u;
    >
    > switch (mods) {
    > case MOD_NONE:
    > case MOD_H:
    > case MOD_HH:
    > u = va_arg(*ap, unsigned int);
    > break;
    > case MOD_L:
    > u = va_arg(*ap, unsigned long int);
    > break;
    > case MOD_L:
    > u = va_arg(*ap, unsigned long long int);
    > break;
    > case MOD_Z:
    > u = va_arg(*ap, size_t);
    > break;
    > }
    > return u;
    > }
    >
    > On my x86 32-bit system this compiles and works as intended. va_list
    > is simply a pointer. When I ported to x86_64 this produces compiler
    > warnings. On that system va_list is an array containing 1 element of
    > a struct type. Therefore, &ap has type struct __va_list_tag ** while
    > get_uint() expects an va_list* which is struct __va_list_tag (*)[1].
    >
    > As a quick work-around I have put the va_list into a struct of which I
    > pass the address into get_uint(). This works on both systems and
    > seems to be portable but looks really ugly:
    >
    > static int xprintf(struct callback *cb, const char *fmt, va_list ap)
    > {
    > va_struct aps;
    > va_copy(aps.ap, ap);
    >
    > ...;
    > switch (conv_spec) {
    > case 'u':
    > u = get_uint(conv_mods, &aps);
    > ...;
    > }
    > va_end(aps.ap);
    > }
    >
    > static unsigned long long get_uint(int mods, struct va_struct *aps)
    > {
    > ...;
    > u = va_arg(aps->ap, unsigned int);
    > ...;
    > }
    >
    > Isn't there a cleaner way to portably pass a va_list to a function by
    > reference?


    Two possibilities:

    1. change xprintf() to take a (va_list *) rather than a (va_list).
    (IMO that's a better solution in general, assuming you have control
    over the callers of xprintf().) This also simplifies the code in
    the body of xprintf().

    2. (if 1 isn't feasible) as per your revised solution, but you don't
    need 'va_struct', just have a local va_list variable, eg,

    /* ... in xprintf() ... */

    va_list z;
    va_copy( z, ap );
    ...

    u = get_uint( conv_mods, &z );
    ...

    va_end( z );


    /* ... get_uint(), etc, still use (va_list *) ... */


    > Also, what is the reason to typedef va_list as an array containing one
    > struct instead of just doing a typedef struct __va_list_tag va_list,
    > in which case no problem would occur with my original code? [snip]


    To keep developers who write portable C code on their toes. :)
    Tim Rentsch, Nov 23, 2010
    #3
  4. Urs Thuermann

    Tim Rentsch Guest

    China Blue Thunder <> writes:

    > In article <>,
    > Urs Thuermann <> wrote:
    >
    >> How can I pass a va_list to another function by reference? In a
    >> simple libc implementation I have some functions as follows:

    >
    > I don't know if this will work, but you might trying passing the va_list by
    > value and returning it as the function value. [snip]


    Obviously this approach won't work is va_list is an array type.
    Tim Rentsch, Nov 23, 2010
    #4
  5. China Blue Thunder <> writes:

    > I don't know if this will work, but you might trying passing the
    > va_list by value and returning it as the function value. The other
    > function return is passed by reference. The problem is var args is a
    > bit of magic from long ago and it doesn't always follow the same
    > rules as everyone else.
    >
    > > static int xprintf(struct callback *cb, const char *fmt, va_list ap)
    > > {
    > > ...
    > > switch (conv_spec) {
    > > case 'u':

    > ap = get_uint(conv_mods, ap, &u);


    > static va_list get_uint(int mods, va_list ap, unsigned long long *u)
    > > {
    > > switch (mods) {
    > > case MOD_NONE:
    > > case MOD_H:
    > > case MOD_HH:

    > *u = va_arg(ap, unsigned int);
    > > break;


    This is exactly the code I had before I ported to 64 bit. When that
    didn't work because ap is an array you cannot assign to, I changed it
    to u = get_uint(mods, &ap) which also didn't work so I created the
    va_struct to pass a pointer tp a copy of ap.

    urs
    Urs Thuermann, Nov 23, 2010
    #5
  6. Tim Rentsch <> writes:

    > Two possibilities:
    >
    > 1. change xprintf() to take a (va_list *) rather than a (va_list).
    > (IMO that's a better solution in general, assuming you have control
    > over the callers of xprintf().) This also simplifies the code in
    > the body of xprintf().


    I have control of the callers of xprintf() but I don't see how this
    helps since changing xprint() only shifts the problem to the callers
    of xprintf(), like

    int vfprintf(FILE *fp, const char *fmt, va_list ap)
    {
    struct callback cb = { file_out, fp };

    return xprintf(&cb, fmt, ap);
    }

    I cannot change this to

    return xprintf(&cb, fmt, &ap);

    for the same reason &ap in xprintf() didn't work portably, and I
    cannot change vfprintf() to take va_list* instead of va_list. I could
    do the va_copy(z, ap) and pass &z as you show below in both,
    vfprintf() and vsnprintf(), but then I prefer your 2. solution.

    > 2. (if 1 isn't feasible) as per your revised solution, but you don't
    > need 'va_struct', just have a local va_list variable, eg,
    >
    > /* ... in xprintf() ... */
    >
    > va_list z;
    > va_copy( z, ap );
    > ...
    >
    > u = get_uint( conv_mods, &z );
    > ...
    >
    > va_end( z );
    >
    >
    > /* ... get_uint(), etc, still use (va_list *) ... */


    Ah right. I have overlooked that for a local variable z of array type
    (i.e. va_list) the expression &z gives the needed type va_list* as
    opposed to a parameter of array type.
    Thanx.

    > To keep developers who write portable C code on their toes. :)


    (-8

    urs
    Urs Thuermann, Nov 23, 2010
    #6
  7. pete <> writes:

    > But, neither the version shown above
    > nor the original version of get_uint,
    > make any attempt to change the value of ap from xprintf.


    Yes they do! In my original version

    static int xprintf(struct callback *cp, const char *fmt, va_list ap)
    {
    ...
    u = get_uint(conv_mods, &ap);
    ...
    }

    static unsigned long long get_uint(int mods, va_list *ap)
    {
    ...
    u = va_arg(*ap, unsigned int);
    ...
    }

    the va_arg() macro is called with *ap where ap points to the ap
    variable of xprintf() so that one is modified. This, however, does
    not work when va_list is typedef'd to be an array type.

    My workaround was to copy the ap argument to a va_list variable in
    xprintf() and pass a pointer to that (my cumbersome definition of
    va_struct to hold that copy turned out to be unnecessary). Here also
    get_uint() operates on the pointer and so does modify the copy of ap
    in xprintf(). After removing the unnecessary struct it looks like
    this:

    static int xprintf(struct callback *cp, const char *fmt, va_list ap0)
    {
    va_list ap;
    va_copy(ap, ap0);
    ...
    u = get_uint(conv_mods, &ap);
    ...
    va_end(ap);
    }

    static unsigned long long get_uint(int mods, va_list *ap)
    {
    ...
    u = va_arg(*ap, unsigned int);
    ...
    }

    This works whether or not va_list is an array type, and it should be
    portable.

    Your suggestion was not to pass a pointer va_list* but a va_list
    instead:

    static int xprintf(struct callback *cp, const char *fmt, va_list ap)
    {
    ...
    u = get_uint(conv_mods, ap);
    ...
    }

    static unsigned long long get_uint(int mods, va_list ap)
    {
    ...
    u = va_arg(ap, unsigned int);
    ...
    }

    Here, xprintf() creates a new copy of ap each time it calls get_uint()
    and get_uint() would only work on that copy. Funnily, if va_list is
    an array type, the ap parameter is only a pointer to the first array
    element, get_uint() would modify that and the code would again work as
    intended. But you cannot count on this.

    BTW, I think because va_list is allowed to be an array type the ISO
    9899 standard says in 7.15

    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.

    > So, why is it important to have the ability
    > to change the value of ap from xprintf?


    Because successive calls to get_uint(), get_int(), and get_float()
    should give the next argument each time, and not always the same
    argument of the variable argument list. The xprintf() function looks
    like this

    static int xprintf(struct callback *cp, const char *fmt, va_list ap0)
    {
    va_list ap;
    va_copy(ap, ap0);

    while (c = *fmt) {
    if (c == '%') {
    /* process optional flags */
    ...
    /* process optional width */
    ...
    /* process optional precision */
    ...
    /* process optional modifiers */
    ...
    /* process conversion specifier */
    switch (c = *fmt) {
    case 'd':
    case 'i':
    n = get_int(conv_mods, &ap);
    ...
    break;
    case 'u':
    u = get_uint(conv_mods, &ap);
    ...
    break;
    case 'x':
    u = get_uint(conv_mods, &ap);
    ...
    break;
    case 'f':
    case 'g':
    ...
    ld = get_float(conv_mods, &ap);
    ...
    case 'p':
    p = va_arg(ap, void *);
    ...
    case 's':
    ccp = va_arg(ap, const char *);
    ...
    ...

    } else {
    /* print until next % */
    }
    }
    va_end(ap);

    return total_len;
    }

    As you see, the get_*() functions are called in a loop together with
    direct access to ap through the va_arg() macro. The get_*() functions
    must therefore modify the ap variable to point to the next argument.

    urs
    Urs Thuermann, Nov 23, 2010
    #7
  8. pete <> writes:

    > I don't think that the va_list type from <stdio.h> can be an array
    > you cannot assign to.


    I see nothing in section 7.15 of ISO 9899 that would prevent this.

    In fact, the paragraph from 7.15 that I cited in my other post
    indicates that such implementations may exist. And I have given an
    example of one such implementation.

    > The reason that I think that, is because <stdio.h> declares several
    > functions which have va_list type parameters.


    Yes, but that is also possible with an va_list defined as an array
    type, as I have pointed out several times. Only taking the address of
    an va_list that was passed to a function as an function argument won't
    work, since that address would then not be an va_list* but the address
    of a pointer to the first array element.

    urs
    Urs Thuermann, Nov 23, 2010
    #8
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Rich Herrick

    reference to va_list

    Rich Herrick, Jan 16, 2005, in forum: C++
    Replies:
    0
    Views:
    433
    Rich Herrick
    Jan 16, 2005
  2. Shea Martin

    passing a va_list to sprintf

    Shea Martin, Oct 31, 2003, in forum: C Programming
    Replies:
    3
    Views:
    920
    Shea Martin
    Oct 31, 2003
  3. Andrej Prsa

    Passing va_list two times?

    Andrej Prsa, Mar 4, 2004, in forum: C Programming
    Replies:
    2
    Views:
    816
    those who know me have no need of my name
    Mar 4, 2004
  4. Replies:
    21
    Views:
    3,148
    Tim Rentsch
    Feb 12, 2009
  5. Vaclav Haisman
    Replies:
    2
    Views:
    1,115
    Vaclav Haisman
    May 15, 2010
Loading...

Share This Page