![]() |
use of va_arg
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); } ~ |
Re: use of va_arg
On 09/03/2012 06:36 AM, sinbad wrote:
> 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. > case 2: > func_two(a, va_arg(list, char), va_arg(list, char)); > break; -- James Kuyper |
Re: use of va_arg
On 9/3/2012 6:36 AM, sinbad wrote:
> 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. -- Eric Sosman esosman@ieee-dot-org.invalid "The speed at which the system fails is usually not important." |
Re: use of va_arg
On Monday, September 3, 2012 6:56:09 PM UTC+5:30, Eric Sosman wrote:
> On 9/3/2012 6:36 AM, sinbad wrote: > > > 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. > > > > -- > > Eric Sosman > > esosman@ieee-dot-org.invalid > > "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 |
Re: use of va_arg
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. |
Re: use of va_arg
On 9/3/2012 1:06 PM, sinbad wrote:
>[...] > 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. -- Eric Sosman esosman@ieee-dot-org.invalid "The speed at which the system fails is usually not important." |
Re: use of va_arg
sinbad <sinbad.sinbad@gmail.com> writes:
> On Monday, September 3, 2012 6:56:09 PM UTC+5:30, Eric Sosman wrote: >> 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. |
Re: use of va_arg
On 9/3/2012 9:26 AM, Eric Sosman wrote:
> On 9/3/2012 6:36 AM, sinbad wrote: >>[...] >> 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. -- Eric Sosman esosman@ieee-dot-org.invalid "The speed at which the system fails is usually not important." |
Re: use of va_arg
Eric Sosman <esosman@ieee-dot-org.invalid> writes:
> On 9/3/2012 9:26 AM, Eric Sosman wrote: >> On 9/3/2012 6:36 AM, sinbad wrote: >>>[...] >>> 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. |
| All times are GMT. The time now is 08:20 AM. |
Powered by vBulletin®. Copyright ©2000 - 2013, vBulletin Solutions, Inc.
SEO by vBSEO ©2010, Crawlability, Inc.