va_list * and function parameters?

S

skillzero

I asked a while back taking the address of a va_list function
parameter and passing it down to other functions to update the
original va_list via va_arg( *args, int ) and the general consensus
was that it was safe. I did that and it seemed to work fine.

I finally started using a compiler that implements va_list as an array
type and now I get compile errors because taking the address of an
array-based va_list function parameter ends up giving me the
equivalent of <underlying_type **> because the array type is
automatically converted to a pointer type when used as a function
parameter.

Is there any way to portably take the address of a va_list function
parameter? If not, is there a portable way to test if va_list is
implemented as an array type at preprocessor time and conditionalize
my code appropriately to handle either array or non-array based
va_list's?
 
T

Tim Rentsch

I asked a while back taking the address of a va_list function
parameter and passing it down to other functions to update the
original va_list via va_arg( *args, int ) and the general consensus
was that it was safe. I did that and it seemed to work fine.

I finally started using a compiler that implements va_list as an array
type and now I get compile errors because taking the address of an
array-based va_list function parameter ends up giving me the
equivalent of <underlying_type **> because the array type is
automatically converted to a pointer type when used as a function
parameter.

Is there any way to portably take the address of a va_list function
parameter? If not, is there a portable way to test if va_list is
implemented as an array type at preprocessor time and conditionalize
my code appropriately to handle either array or non-array based
va_list's?

It would help if code were posted showing what was tried.

Did you try something like this?

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

void
foo_vprint( const char *s, va_list *v ){
vprintf( s, *v );
}

void
foo_print( const char *s, ... ){
va_list arguments;
va_start( arguments, s );
foo_vprint( s, &arguments );
va_end( arguments );
}

Please give that a try, and a report back after
(including error text, if there is any).
 
S

skillzero

It would help if code were posted showing what was tried.

Your example worked, but it's using a local va_list argument. My case
is getting a va_list passed to it and needs to get a va_list * to pass
to other functions (so they can update the original va_list). Using
your example, if I do this, it fails:

void foo_vprint( const char *s, va_list *v )
{
vprintf( s, *v );
}

void foo_vprint2( const char *s, va_list v )
{
foo_vprint( s, &v ); // Fails: incompatible pointer type error.
}

I understand why it fails (array type converted to pointer type when
it's a function parameter and that causes the address of it to be a
**), but I don't know if there's a way to work around it in a portable
way.
 
T

Tim Rentsch

Your example worked, but it's using a local va_list argument. My case
is getting a va_list passed to it and needs to get a va_list * to pass
to other functions (so they can update the original va_list). Using
your example, if I do this, it fails:

void foo_vprint( const char *s, va_list *v )
{
vprintf( s, *v );
}

void foo_vprint2( const char *s, va_list v )
{
foo_vprint( s, &v ); // Fails: incompatible pointer type error.
}

I understand why it fails (array type converted to pointer type when
it's a function parameter and that causes the address of it to be a
**), but I don't know if there's a way to work around it in a portable
way.

If you need to update the original va_list (that is, the argument
passed to foo_vprint2()), then the parameter type in foo_vprint2()
must be (va_list *); otherwise, the original va_list argument
won't get updated on some platforms.

If what needs to be updated is not the original va_list argument,
but the va_list used as an argument in calls made in foo_vprint2(),
how about this:

void
foo_vprint2( const char *s, va_list v ){
va_list v_local;
va_copy( v_local, v );
foo_vprint( s, &v_local );
va_end( v_local );
}

Not especially pretty, but it should work.
 
P

Peter Nilsson

Your example worked, but it's using a local va_list argument.
My case is getting a va_list passed to it and needs to get a
va_list * to pass to other functions (so they can update the
original va_list). Using your example, if I do this, it fails:

void foo_vprint( const char *s, va_list *v )
{
    vprintf( s, *v );
}

void foo_vprint2( const char *s, va_list v )
{
    foo_vprint( s, &v ); // Fails: incompatible pointer
type error.
}

I understand why it fails (array type converted to pointer
type when it's a function parameter and that causes the
address of it to be a **), but I don't know if there's a
way to work around it in a portable way.

The portable way is for foo_vprint2 to take a va_list *
as well, and for it to simply pass it as is. If that doesn't
match your scenario, then you probably need va_copy.

Clearly there's some context missing since there's no
obvious reason for foo_vprint2's existance, or why it
needs foo_vprint as an ancillary function.
 
S

skillzero

If what needs to be updated is not the original va_list argument,
but the va_list used as an argument in calls made in foo_vprint2(),
how about this:

    void
    foo_vprint2( const char *s, va_list v ){
        va_list v_local;
        va_copy( v_local, v );
        foo_vprint( s, &v_local );
        va_end( v_local );
    }

Not especially pretty, but it should work.

Yeah, I think that will work, but unfortunately, one of environments I
have to support is Visual Studio .NET 2003 for one of our embedded
build systems and it doesn't support va_copy.
 
B

Ben Pfaff

Yeah, I think that will work, but unfortunately, one of environments I
have to support is Visual Studio .NET 2003 for one of our embedded
build systems and it doesn't support va_copy.

Can you just replace it?

#include <stdarg.h>

#ifndef va_copy
#define va_copy(dst, src) (dst) = (src)
#endif
 
S

skillzero

Can you just replace it?

        #include <stdarg.h>

        #ifndef va_copy
        #define va_copy(dst, src) (dst) = (src)
        #endif

I think that will work for some cases, but for array-based va_list's
and possibly others, it probably won't work.
 
S

skillzero

The portable way is for foo_vprint2 to take a va_list *
as well, and for it to simply pass it as is. If that doesn't
match your scenario, then you probably need va_copy.

Clearly there's some context missing since there's no
obvious reason for foo_vprint2's existance, or why it
needs foo_vprint as an ancillary function.

I simplified things for the example, but in the real code, my code
provides the underlying implementation of printf, vprintf, etc. People
calling vprintf will pass a va_list they've allocated locally so I
have to handle that case. My core code supports extensions to the
standard set of printf format specifiers (which could benefit from
being able to update the va_list) and I want to use the same code for
both the standard printf, vprintf, etc. callers as well as my non-
standard APIs.
 
S

Spiros Bousbouras

I think that will work for some cases, but for array-based va_list's
and possibly others, it probably won't work.

You can still try it and see if it works for the environments
which don't have their own va_copy. You should also
consider bringing it up with your compiler vendor.
 
N

Nate Eldredge

I think that will work for some cases, but for array-based va_list's
and possibly others, it probably won't work.

#define va_copy(dst, src) memcpy((dst), (src), sizeof(src))

might be better.

It's conceivable that va_list contains pointers to other objects which
are created by va_start, in which case this won't be good enough
either. If you want to know for sure, you'll have to RTFH (Read The
Obscenity Header). And if you find that va_start et al expand to
function calls, you'll have to RTFS or RTFB depending on how cozy you
are with Microsoft.
 
T

Tim Rentsch

Yeah, I think that will work, but unfortunately, one of environments I
have to support is Visual Studio .NET 2003 for one of our embedded
build systems and it doesn't support va_copy.

Sigh. There was always something vaguely disquieting about
how va_list and friends were specified. Now I know why.

Under the circumstances, what I would try, and probably end up
doing, is something like this:

#if VA_LIST_IS_ARRAY_TYPE
# define VA_LIST_PARAMETER_ADDRESS(v) ((va_list*) (v))
#else
# define VA_LIST_PARAMETER_ADDRESS(v) (&(v))
#endif

void
foo_vprint2( const char *s, va_list v ){
foo_vprint( s, VA_LIST_PARAMETER_ADDRESS(v) );
}

and then write a short, special purpose shell script that would
generate a small test program, run it through the compiler, read
the diagnostics (platform specific -- UGH!), and automatically
generate a file containing the #define for VA_LIST_IS_ARRAY_TYPE
with the correct value. With the shell script written, an extra
step in the makefile (or whatever is controlling the build) can
cause this configuration file to be produced as a prerequisite
to the file(s) that need the VA_LIST_... macros. Better still,
the generated file can have the preprocessor lines above in it
as well.

Actually, now that I think about it, the C source

#include <stdarg.h>
void foo( va_list v ){ va_list w = v; (void)&w; }

is probably pretty safe for testing whether (va_list) is an array
type or not. If it is, there's a pretty reliable diagnostic; if
not, the file is pretty likely to compile with no diagnostics.
Furthermore, misinterpreting any produced diagnostic would err on
the side of thinking a va_list is an array type, which is likely
to produce a compile-time error for the VA_LIST_PARAMETER_ADDRESS
macro if it isn't.

So right now that's the best suggestion I have for you.
 
T

Tim Rentsch

Ben Pfaff said:
Can you just replace it?

#include <stdarg.h>

#ifndef va_copy
#define va_copy(dst, src) (dst) = (src)
#endif

Sadly, this kind of workaround (and the memcpy() variant as
well, as I realize now) will run into the same kind of
problem as the original -- it's hard to write one that
works both for array types and non-array types.
 
S

skillzero

Actually, now that I think about it, the C source

    #include <stdarg.h>
    void foo( va_list v ){ va_list w = v; (void)&w; }

is probably pretty safe for testing whether (va_list) is an array
type or not.

That seems like a good idea. I tried the following for a runtime test
and it worked on several platforms (some array types, some not). With
casting to avoid compiler errors for the not-taken paths, it could
probably be used to detect the va_list type and use & or not. There's
probably some platform where va_list * and void * can't be compared
reliably (Cray?), making my test invalid though.

int VAListIsArray(int x, ...)
{
int isArray;
va_list args;
va_list * argsPtr;

va_start(args, x);
argsPtr = &args;
isArray = (((void *) argsPtr ) == ((void *) args));
va_end(args);

return(isArray);
}

I may just have to give up on the feature I'm trying to add or deal
with the maintenance issues of having a va_list * and va_list variants
of my underly code. Some of the platforms I have to deal with build
through custom IDE's provided by the vendor where it's not possible to
have it automatically run scripts to test for things like this.

Thanks for the help though.
 
T

Tim Rentsch

That seems like a good idea. I tried the following for a runtime test
and it worked on several platforms (some array types, some not). With
casting to avoid compiler errors for the not-taken paths, it could
probably be used to detect the va_list type and use & or not. There's
probably some platform where va_list * and void * can't be compared
reliably (Cray?), making my test invalid though.

int VAListIsArray(int x, ...)
{
int isArray;
va_list args;
va_list * argsPtr;

va_start(args, x);
argsPtr = &args;
isArray = (((void *) argsPtr ) == ((void *) args));
va_end(args);

return(isArray);
}

I may just have to give up on the feature I'm trying to add or deal
with the maintenance issues of having a va_list * and va_list variants
of my underly code. Some of the platforms I have to deal with build
through custom IDE's provided by the vendor where it's not possible to
have it automatically run scripts to test for things like this.

Thanks for the help though.

Okay, I've had a chance to think about this a little longer now.
I suggest a solution as follows, using a .h/.c file pair (header
wrapping ifndef's not shown):

va_stuff.h:

#include <stdarg.h> /* just for convenience */

#define VA_LIST_IS_ARRAY_TYPE (0)

#if VA_LIST_IS_ARRAY_TYPE
# define VA_PARAMETER_LIST_ADDRESS(v) ((va_list*)(v))
#else
# define VA_PARAMETER_LIST_ADDRESS(v) (&(v))
#endif

va_stuff.c:

#include "va_stuff.h"

extern void
takes_a_va_list( va_list v ){
va_list *p = VA_PARAMETER_LIST_ADDRESS(v);
(void) &p;
}

(Probably this version of VA_PARAMETER_LIST_ADDRESS is exactly the same
as what I did before; I didn't check it against the previous one.)
Compile va_stuff.c with -Werror, or whatever the local equivalent is.
Any other file that needs to take the address of a va_list parameter
should #include "va_stuff.h" and use the macro.

Of course, the #define VA_LIST_IS_ARRAY_TYPE will give the wrong
value on platforms where va_list is an array type; but that's
okay, because the result will be a compilation error -- change the
0 to a 1, and everything is good to go. That also works the other
way -- if the #define indicates that va_list is an array type on a
platform where it isn't, again the result is a compilation error.
If va_stuff.c compiles without diagnostics, things are set okay,
and the macro for getting the address of a va_list parameter does
the right thing.

A downside of this approach is that using this macro removes some
type checking. The case where VA_LIST_IS_ARRAY_TYPE is 1 will
allow any pointer type, no questions asked. Fortunately, the most
likely common error (using a (va_list *) rather than a (va_list)
parameter) will do the right thing. Structure and union types
will give compilation errors, and correct type checking takes
place if VA_LIST_IS_ARRAY_TYPE is 0. So that isn't too bad. Plus
using the macro makes these all easy to find.

Of course, it would be nice if we could find a way to figure this
out in the program. And in fact it is possible to write a a small
number of functions that together determine whether va_list is an
array type or not, in a completely reliable and portable way.
(Exercise for the reader.) Unfortunately, that doesn't do any
good, because there's no way to take advantage of the information
in code that is accepted and works reliably on all platforms for
both the array case and the non-array case. So some sort of
preprocessor-based solution is necessary if it's important that
it be usable on any conforming implementation.
 
T

Tim Rentsch

Tim Rentsch said:
And in fact it is possible to write a a small
number of functions that together determine whether va_list is an
array type or not, in a completely reliable and portable way.

I need to amend this statement. It is possible to write code
that's completely portable, but I have to withdraw the claim that
it's completely reliable. I do think it can be made reliable
enough so that it would work on any actual implementation, but
it's hard (or perhaps impossible) to write code that works
against an implementation designed to defeat it.

Still a good exercise for the reader though.
 
B

Ben Pfaff

Tim Rentsch said:
Sadly, this kind of workaround (and the memcpy() variant as
well, as I realize now) will run into the same kind of
problem as the original -- it's hard to write one that
works both for array types and non-array types.

The question is not whether it works for all possible va_list
implementation. It is whether it works for the implementations,
that you care about, that do not define va_copy themselves.
 
T

Tim Rentsch

Tim Rentsch said:
va_stuff.h:

#include <stdarg.h> /* just for convenience */

#define VA_LIST_IS_ARRAY_TYPE (0)

#if VA_LIST_IS_ARRAY_TYPE
# define VA_PARAMETER_LIST_ADDRESS(v) ((va_list*)(v))
#else
# define VA_PARAMETER_LIST_ADDRESS(v) (&(v))
#endif

va_stuff.c:

#include "va_stuff.h"

extern void
takes_a_va_list( va_list v ){
va_list *p = VA_PARAMETER_LIST_ADDRESS(v);
(void) &p;
}

Compile va_stuff.c with -Werror, or whatever the local equivalent is.
Any other file that needs to take the address of a va_list parameter
should #include "va_stuff.h" and use the macro.

Of course, the #define VA_LIST_IS_ARRAY_TYPE will give the wrong
value on platforms where va_list is an array type; but that's
okay, because the result will be a compilation error -- change the
0 to a 1, and everything is good to go. That also works the other
way -- if the #define indicates that va_list is an array type on a
platform where it isn't, again the result is a compilation error.

Another correction -- if a va_list is not an array type, then setting
the #define so as to choose the first branch of the #if /MIGHT/
generate a diagnostic but doesn't have to (e.g., va_list could be a
pointer type).

The default choice of 0 (choosing the #else branch of the #if)
does reliably generate a diagnostic if va_list is an array
type.

Two errors in one posting... I've got to remember not to
respond in haste... :(
 
T

Tim Rentsch

Ben Pfaff said:
The question is not whether it works for all possible va_list
implementation. It is whether it works for the implementations,
that you care about, that do not define va_copy themselves.

I acknowledge the distinction, and also the usefulness of
the observation.

Defining va_copy() as above won't work on any platform where
va_list is an array type (and used on a parameter declared
as a va_list). That consequence is severe enough so that I
would want to look for another possible solution.

This approach does have the nice property that, if used on a
platform where va_list is an array type, it will generate a
diagnostic.
 
L

lawrence.jones

Han from China said:
Can someone please tell me why C1X is in the works when it's pretty
clear the vendors didn't consider C99 important enough?

"Important enough" for what? As I've said many times before, most C
implementors are also C++ implementors and they've had their hands full
trying to implement the C++ standard (complete implementations of which
are as rare as complete implementations of C99 despite its having been
approved a year earlier and generally being considered higher priority
than C). Most current C implementations implement much, if not most, of
C99.

As for why:

1) It's time: it's been 10 years since C99 was approved.

2) There's high demand for support for things like threads and Unicode
characters and strings.

3) There have been three Technical Corrigenda for C99 and it would be
convenient to have an official document that incorporates all those
changes.
 

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,766
Messages
2,569,569
Members
45,043
Latest member
CannalabsCBDReview

Latest Threads

Top