Passing va_list by reference to a function

  • Follow


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
0
Reply Urs 11/22/2010 3:16:19 PM

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
0
Reply pete 11/22/2010 4:38:47 PM


pete <pfiland@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
0
Reply Urs 11/22/2010 7:09:55 PM

In article <ygfd3pxe6d8.fsf@janus.isnogud.escape.de>,
 Urs Thuermann <urs@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. 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);
> 			...
> 			break;
> 		case 'x':
        ap = get_uint(conv_mods, ap, &u);
> 			...
> 		case 'd':
        ap = get_int(conv_mods, ap, &n);
> 			...
> 		...
> 		}
> 	}
> 
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;
> 		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 ap;
> 	}
> 
> 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

-- 
Damn the living - It's a lovely life.           I'm whoever you want me to be.
Silver silverware - Where is the love?       At least I can stay in character.
Oval swimming pool - Where is the love?    Annoying Usenet one post at a time.
Damn the living - It's a lovely life.                I found out what JAFO is.
0
Reply China 11/23/2010 12:11:05 AM

Urs Thuermann <urs@isnogud.escape.de> 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. :)
0
Reply Tim 11/23/2010 1:02:01 AM

China Blue Thunder <chine.bleu@yahoo.com> writes:

> In article <ygfd3pxe6d8.fsf@janus.isnogud.escape.de>,
>  Urs Thuermann <urs@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.
0
Reply Tim 11/23/2010 1:19:40 AM

Urs Thuermann wrote:
> 
> pete <pfiland@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().  

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
0
Reply pete 11/23/2010 3:25:24 AM

China Blue Thunder <chine.bleu@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
0
Reply Urs 11/23/2010 4:38:22 AM

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
0
Reply pete 11/23/2010 5:18:57 AM

Tim Rentsch <txr@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
0
Reply Urs 11/23/2010 5:27:41 AM

In article <4CEB4EC1.1CD1@mindspring.com>, pete <pfiland@mindspring.com> wrote:

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

It's safe to pass in va_list by value as deeply as you want and then run through 
the entire list in one function. Trying to pull off arguments in different 
function or restarting the list can be problematic. A va_list can use 
implementation specific details a normal C program is unaware of.

-- 
Damn the living - It's a lovely life.           I'm whoever you want me to be.
Silver silverware - Where is the love?       At least I can stay in character.
Oval swimming pool - Where is the love?    Annoying Usenet one post at a time.
Damn the living - It's a lovely life.                I found out what JAFO is.
0
Reply China 11/23/2010 6:16:34 AM

pete <pfiland@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
0
Reply Urs 11/23/2010 11:40:52 AM

pete <pfiland@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
0
Reply Urs 11/23/2010 1:10:11 PM

Urs Thuermann wrote:
> 
> pete <pfiland@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
0
Reply pete 11/23/2010 1:45:44 PM

Urs Thuermann wrote:
> 
> pete <pfiland@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.

I understand the problem now.
Thank you.

-- 
pete
0
Reply pete 11/23/2010 2:11:20 PM

14 Replies
843 Views

(page loaded in 0.236 seconds)

Similiar Articles:


















7/23/2012 9:53:27 AM


Reply: