Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Passing va_list by reference to a function

23 views
Skip to first unread message

Urs Thuermann

unread,
Nov 22, 2010, 10:16:19 AM11/22/10
to
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

pete

unread,
Nov 22, 2010, 11:38:47 AM11/22/10
to
Urs Thuermann wrote:

> 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;
> }

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;
}

--
pete

Urs Thuermann

unread,
Nov 22, 2010, 2:09:55 PM11/22/10
to
pete <pfi...@mindspring.com> 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

Message has been deleted

Tim Rentsch

unread,
Nov 22, 2010, 8:02:01 PM11/22/10
to
Urs Thuermann <u...@isnogud.escape.de> 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().

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

unread,
Nov 22, 2010, 8:19:40 PM11/22/10
to
China Blue Thunder <chine...@yahoo.com> writes:

> In article <ygfd3px...@janus.isnogud.escape.de>,


> Urs Thuermann <u...@isnogud.escape.de> 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.

pete

unread,
Nov 22, 2010, 10:25:24 PM11/22/10
to

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

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

--
pete

Urs Thuermann

unread,
Nov 22, 2010, 11:38:22 PM11/22/10
to
China Blue Thunder <chine...@yahoo.com> 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

pete

unread,
Nov 23, 2010, 12:18:57 AM11/23/10
to
Urs Thuermann wrote:

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

I don't think that the va_list type from <stdio.h>
can be an array you cannot assign to.
The reason that I think that, is because <stdio.h>
declares several functions which have va_list type parameters.

--
pete

Urs Thuermann

unread,
Nov 23, 2010, 12:27:41 AM11/23/10
to
Tim Rentsch <t...@alumni.caltech.edu> 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

Message has been deleted

Urs Thuermann

unread,
Nov 23, 2010, 6:40:52 AM11/23/10
to
pete <pfi...@mindspring.com> 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

unread,
Nov 23, 2010, 8:10:11 AM11/23/10
to
pete <pfi...@mindspring.com> 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

pete

unread,
Nov 23, 2010, 8:45:44 AM11/23/10
to
Urs Thuermann wrote:
>
> pete <pfi...@mindspring.com> 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!

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

Thank you.
I didn't understand the problem.
Sorry about that.

--
pete

pete

unread,
Nov 23, 2010, 9:11:20 AM11/23/10
to

I understand the problem now.
Thank you.

--
pete

0 new messages