use of va_arg

S

sinbad

is this ok to use va_arg() while calling a function and passing
it as a parameter,look at the following example. while invoking
func_two() does the parameter passing depend on how args are
parsed.

void
func_one(char a, char b) {
...
}

void
func_two(char a, char b, char c) {
...
}

int
top_level(char a , ...)
{
va_list list;

va_start(list, a);

switch(a) {
case 1:
func_one(a, va_arg(list, char));
break;

case 2:
func_two(a, va_arg(list, char), va_arg(list, char));
break;

default:
printf("error");
}

return(0);
}
~
 
J

James Kuyper

is this ok to use va_arg() while calling a function and passing
it as a parameter,look at the following example. while invoking
func_two() does the parameter passing depend on how args are
parsed.

void
func_one(char a, char b) {
...
}

void
func_two(char a, char b, char c) {
...
}

int
top_level(char a , ...)
{
va_list list;

va_start(list, a);

switch(a) {
case 1:
func_one(a, va_arg(list, char));
break;

A valid use of va_arg() expands into a expression of the specified type,
which you're free to use anywhere that such an expression is used,
including as an argument of a function. This is perfectly fine.
 
E

Eric Sosman

is this ok to use va_arg() while calling a function and passing
it as a parameter,look at the following example. while invoking
func_two() does the parameter passing depend on how args are
parsed.

va_arg(ap,type) yields the value of the next `...' parameter
from `ap', assuming it has type `type'. There is nothing special
about that value: You can store it somewhere, compare it to
something, print it, or use it in a function call just as you
would any other value. If you use it as an argument to a function,
the function's parameter will receive the same value without
knowing that it came from va_arg() -- it's just a value, and its
origin doesn't matter.

However ...
void
func_one(char a, char b) {
...
}

void
func_two(char a, char b, char c) {
...
}

int
top_level(char a , ...)
{
va_list list;

va_start(list, a);

switch(a) {
case 1:
func_one(a, va_arg(list, char));

No problem here, assuming there is in fact a next `...'
parameter and its type is `char'. func_one() receives the
value of `a' and whatever value va_arg() produces.
break;

case 2:
func_two(a, va_arg(list, char), va_arg(list, char));

Problem: Argument evaluations in function calls are not made
in sequence, but in any order the compiler chooses. They may
even be interleaved; no sequence points separate the evaluations.

So, what does "next" mean under these conditions? Nothing!
There's no way to know which of the two va_arg() expressions will
evaluate first, and no way to know which retrieved `...' parameter
will be the second argument to func_two() and which the third.
The order of evaluation is unspecified.

It's even worse than that, because "Each invocation of the
va_arg macro modifies ap" (7.16.1.1p2). What have you heard about
modifying the same object twice without an intervening sequence
point? If your program executes this line, its behavior is not
just unspecified, but undefined.
 
S

sinbad

va_arg(ap,type) yields the value of the next `...' parameter

from `ap', assuming it has type `type'. There is nothing special

about that value: You can store it somewhere, compare it to

something, print it, or use it in a function call just as you

would any other value. If you use it as an argument to a function,

the function's parameter will receive the same value without

knowing that it came from va_arg() -- it's just a value, and its

origin doesn't matter.



However ...









No problem here, assuming there is in fact a next `...'

parameter and its type is `char'. func_one() receives the

value of `a' and whatever value va_arg() produces.







Problem: Argument evaluations in function calls are not made

in sequence, but in any order the compiler chooses. They may

even be interleaved; no sequence points separate the evaluations.



So, what does "next" mean under these conditions? Nothing!

There's no way to know which of the two va_arg() expressions will

evaluate first, and no way to know which retrieved `...' parameter

will be the second argument to func_two() and which the third.

The order of evaluation is unspecified.



It's even worse than that, because "Each invocation of the

va_arg macro modifies ap" (7.16.1.1p2). What have you heard about

modifying the same object twice without an intervening sequence

point? If your program executes this line, its behavior is not

just unspecified, but undefined.



--

Eric Sosman

(e-mail address removed)

"The speed at which the system fails is usually not important."

ok, that's what i thought.

here is what i am trying to achieve.
i have several functions func_one(), func_two(), func_three() ...
each taking different number of parameters. i want to consolidate the
function calls func_one() and func_two() and so on, into one top_level_fn()
now the problem is how do i pass the args for func_one, two .. so i am forced
to use var args in top_level_fn(). Inside this top_level_fn() i either have to
use a variable for each different type of arg, such that i can pass to the individual function, this might make the top_level_fn() crowded with variable
declarations, one for each argument that might be required for each function_one,two,... or i can pass the va_list parameter as is to func_one(), two() ... which i don't want to, because these functions are called directly
from different locations also ... is there a better of way of doing this.

thanks
sinbad
 
V

Varun Tewari

I don't see any serious issue with your approach.
However, there's a good chance that your code won't be portable.

I would rather, use va_list in parent function, and do a memcpy of parameters in a local copy, and then keep passing them through different functions.
 
E

Eric Sosman

[...]
here is what i am trying to achieve.
i have several functions func_one(), func_two(), func_three() ...
each taking different number of parameters. i want to consolidate the
function calls func_one() and func_two() and so on, into one top_level_fn()
now the problem is how do i pass the args for func_one, two .. so i am forced
to use var args in top_level_fn(). Inside this top_level_fn() i either have to
use a variable for each different type of arg, such that i can pass to the individual function, this might make the top_level_fn() crowded with variable
declarations, one for each argument that might be required for each function_one,two,... or i can pass the va_list parameter as is to func_one(), two() ... which i don't want to, because these functions are called directly
from different locations also ... is there a better of way of doing this.

My first thought is "Why consolidate?" Since each call to
top_level_fn() must pass an argument that describes which of
func_one(), func_two(), ... to call, it's no more convenient to
call top_level_fn(USE_FUNC_ONE, 42) than to call func_one(42)
directly. In fact, it's a little less convenient, because the
compiler has no idea what the `...' parameters are and cannot
check the arguments for agreement with them: If you write
top_level_fn(USE_FUNC_ONE) the compiler will be perfectly
happy, even though func_one() would have produced an error
message for the missing argument.

However, it's possible that top_level_fn() adds value in
some way: Maybe it logs something or does some pre- or post-
computation of some kind in addition to calling func_xxx().
Okay: If the extras are enough to offset the inconvenience
and loss of type safety, fine.

In that case, I'd probably write something like

int top_level_fn(int which, ...) {
va_list ap;
int result;

extra_stuff_before_the_call();
va_start(ap, which);
switch (which) {
case USE_FUNC_ONE: {
char arg1 = va_arg(ap, char);
result = func_one(arg1);
break;
}
case USE_FUNC_TWO: {
char arg1 = va_arg(ap, char);
double arg2 = va_arg(ap, double);
result = func_two(arg1, arg2);
break;
}
...
default:
fprintf(stderr, "Unknown function code %d!\n", which);
exit(EXIT_FAILURE);
break;
}
va_end(ap); // Don't forget this!
extra_stuff_after_the_call();
return result;
}

Yes, this uses a variable for each func_xxx() argument (even
for func_one(), where it isn't strictly necessary). However,
the variables' scopes are pretty well localized and do not
interfere with each other, so I don't think there's much of
a problem there.
 
T

Tim Rentsch

sinbad said:
On 9/3/2012 6:36 AM, sinbad wrote:
[snip]

here is what i am trying to achieve. i have several functions
func_one(), func_two(), func_three() ... each taking different number
of parameters. i want to consolidate the function calls func_one() and
func_two() and so on, into one top_level_fn() now the problem is how
do i pass the args for func_one, two .. so i am forced to use var args
in top_level_fn(). [snip]

Here's an alternative. Rather than trying to do everything
in the top level function, write a short wrapper function
for each subfunction, taking a 'va_list *' argument, with
the wrapper functions being responsible for further argument
collection. Thus, eg,

... definitions for func_one(), func_two(), etc ...

void
func_one_va( char a, va_list *args ){
func_one( a, va_arg( *args, char ) );
}

void
func_two_va( char a, va_list *args ){
char b = va_arg( *args, char ), c = va_arg( *args, char );
func_two( a, b, c );
}

... and so forth ...

int
top_level( char a , ... ){
va_list list;
va_start( list, a );

switch(a) {
case 1:
func_one_va( a, &list );
break;

case 2:
func_two_va( a, &list );
break;

... and so forth ...

default:
printf("error");
}

va_end( list );
return 0;
}

Now the top level function doesn't have to know about
what's needed for each subfunction's argument collection;
each one collects its own arguments as needed.
 
E

Eric Sosman

[...]
int
top_level(char a , ...)
{
va_list list;
va_start(list, a);

switch(a) {
case 1:
func_one(a, va_arg(list, char));

No problem here, assuming there is in fact a next `...'
parameter and its type is `char'. func_one() receives the
value of `a' and whatever value va_arg() produces.
[...]

Oops! I got sidetracked by other issues in the post and its
code, and missed something rather basic. There *is* a problem
here, because the argument expressions corresponding to `...'
parameters are subject to the "default argument promotions." This
means that a `char'-valued argument expression will be converted
to an `int' (or to an `unsigned int' on some machines), so the
called function receives an `int' and not a `char'. You must tell
va_arg the "as received" type, not the type as it was prior to
promotion. (7.15.1.1p2 lists a few cases where a type clash is
permissible; this is not one of them.)

More generally, it's hazardous to use any promotable type as
an argument corresponding to `...' parameters because it's hard
to know how to retrieve it with va_arg. A `float' argument always
promotes to `double', but "narrow" integer types are trickier:

- `signed char' promotes to `int'. `char' and `unsigned char'
promote to `int' on most machines, `unsigned int' on a few.

- `short' promotes to `int'. `unsigned short' promotes to
`int' on many machines, `unsigned int' on a few ("more").

- `enum' promotes to either `int' or `unsigned int', depending
on the underlying integer type the compiler chooses. (The
named constants are always `int' and do not promote; I'm
talking about `enum'-valued expressions.)

- Some of the <stdint.h> types may promote to `int', some to
`unsigned int', and some will not promote at all but will
come across as themselves. Which behave each way will vary
from machine to machine.

- "Semi-opaque" types like `size_t', `ptrdiff_t', `time_t'
and so on are not usually subject to promotion, but might
promote to `int' or `unsigned int' on some systems.

With lots of #if testing it is sometimes possible to code
the right va_arg invocation, as in

#include <limits.h>
...
#if CHAR_MAX <= INT_MAX
// `char' promotes to `int' on this machine
char c = va_arg(list, int);
#else
// `char' promotes to `unsigned int' on this machine
char c = va_arg(list, unsigned int);
#endif

.... and similarly for the other flavors of `char' and for the
variations on `short'. If you're using <stdint.h> types, you
can use its xxxx_MAX macros the same way, adding a third branch
for the non-promotion case. Some of the semi-opaque types have
xxxx_MAX macros and can be tested this way, some lack them and
can't be. `enum' types have no xxxx_MAX you could test.

A related issue arises when passing pointer arguments to `...'
functions: You may need casts at the point of call to coerce the
argument pointer to the type the called function will use with
va_arg. In particular, if you want to pass NULL as an argument
you nearly always need to cast it, because NULL might expand to
an unadorned 0 which the compiler will treat like an `int' rather
than like a pointer. So,

char *concatenate(char *first, ... /* string pointers */ );
...
char *color = "green";
char *food = "ham";
...
char *wrong = concatenate("I do not like ",
color, " eggs and ", food, ".", NULL);
...
char *right = concatenate("I do not like ",
color, " eggs and ", food, ".", (char*)NULL);

The take-away: Don't use promotable or even potentially
promotable argument expressions for `...' parameters, because
all those #if's will clutter your code abominably (and give you
lots of extra opportunities to botch something). Sorry for
overlooking this the first time around.
 
T

Tim Rentsch

Eric Sosman said:
[...]
int
top_level(char a , ...)
{
va_list list;
va_start(list, a);

switch(a) {
case 1:
func_one(a, va_arg(list, char));

No problem here, assuming there is in fact a next `...'
parameter and its type is `char'. func_one() receives the
value of `a' and whatever value va_arg() produces.
[...]

Oops! I got sidetracked by other issues in the post and its
code, and missed something rather basic. There *is* a problem
here, because the argument expressions corresponding to `...'
parameters are subject to the "default argument promotions." This
means that a `char'-valued argument expression will be converted
to an `int' (or to an `unsigned int' on some machines), so the
called function receives an `int' and not a `char'. You must tell
va_arg the "as received" type, not the type as it was prior to
promotion. (7.15.1.1p2 lists a few cases where a type clash is
permissible; this is not one of them.)

More generally, it's hazardous to use any promotable type as
an argument corresponding to `...' parameters because it's hard
to know how to retrieve it with va_arg. A `float' argument always
promotes to `double', but "narrow" integer types are trickier:

- `signed char' promotes to `int'. `char' and `unsigned char'
promote to `int' on most machines, `unsigned int' on a few.

- `short' promotes to `int'. `unsigned short' promotes to
`int' on many machines, `unsigned int' on a few ("more").

- `enum' promotes to either `int' or `unsigned int', depending
on the underlying integer type the compiler chooses. (The
named constants are always `int' and do not promote; I'm
talking about `enum'-valued expressions.)

- Some of the <stdint.h> types may promote to `int', some to
`unsigned int', and some will not promote at all but will
come across as themselves. Which behave each way will vary
from machine to machine.

- "Semi-opaque" types like `size_t', `ptrdiff_t', `time_t'
and so on are not usually subject to promotion, but might
promote to `int' or `unsigned int' on some systems.

With lots of #if testing it is sometimes possible to code
the right va_arg invocation, as in

#include <limits.h>
...
#if CHAR_MAX <= INT_MAX
// `char' promotes to `int' on this machine
char c = va_arg(list, int);
#else
// `char' promotes to `unsigned int' on this machine
char c = va_arg(list, unsigned int);
#endif

... and similarly for the other flavors of `char' and for the
variations on `short'. If you're using <stdint.h> types, you
can use its xxxx_MAX macros the same way, adding a third branch
for the non-promotion case. Some of the semi-opaque types have
xxxx_MAX macros and can be tested this way, some lack them and
can't be. `enum' types have no xxxx_MAX you could test.

[snip unrelated]

These concerns are overblown. Any signed type no larger than int
may always be read using 'int'; any unsigned type no larger than
unsigned int may always be read using 'unsigned int'. It's easy
enough to write a macro (conditionally chosen between two
possible definitions) for reading 'char' arguments. Types like
size_t or those in <stdint.h> can be accessed safely by writing
wrapper macros, eg 'va_size_t( list )' or 'va_int32_t( list )',
and these (conditionally selected sets of) definitions can be
written just once. Enums are kind of a pain, at least in theory,
because there is no easy way to tell if the type chosen for enum
is larger than int/unsigned int. In practical terms, though, any
enum-valued expression can be read with 'int', because any
enumeration constant has a value representable as an int (and
this is even a constraint, so it's checked at compile time),
so reading with 'int' will give the right result for enum-valued
expressions whose value is one of the defined constants. Of
course, malicious compilers could choose enumeration types
that are larger than int, but most real compilers won't, and
for those that do doing a check using sizeof (incorporated into
an access macro that wraps the call to va_arg) should suffice
for all practical purposes.
 

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

Forum statistics

Threads
473,764
Messages
2,569,566
Members
45,041
Latest member
RomeoFarnh

Latest Threads

Top