Passing va_list by reference to a function

U

Urs Thuermann

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
 
U

Urs Thuermann

pete said:
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
 
T

Tim Rentsch

Urs Thuermann said:
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. :)
 
T

Tim Rentsch

China Blue Thunder said:
Urs Thuermann said:
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.
 
U

Urs Thuermann

China Blue Thunder said:
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.

ap = get_uint(conv_mods, ap, &u);
static va_list get_uint(int mods, va_list ap, unsigned long long *u)

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
 
U

Urs Thuermann

Tim Rentsch said:
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
 
U

Urs Thuermann

pete said:
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
 
U

Urs Thuermann

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

Ask a Question

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

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,579
Members
45,053
Latest member
BrodieSola

Latest Threads

Top