use of va_arg

Discussion in 'C Programming' started by sinbad, Sep 3, 2012.

  1. sinbad

    sinbad Guest

    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);
    }
    ~
    sinbad, Sep 3, 2012
    #1
    1. Advertising

  2. sinbad

    James Kuyper Guest

    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
    James Kuyper, Sep 3, 2012
    #2
    1. Advertising

  3. sinbad

    Eric Sosman Guest

    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
    d
    "The speed at which the system fails is usually not important."
    Eric Sosman, Sep 3, 2012
    #3
  4. sinbad

    sinbad Guest

    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
    >
    > d
    >
    > "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
    sinbad, Sep 3, 2012
    #4
  5. sinbad

    Varun Tewari Guest

    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.
    Varun Tewari, Sep 3, 2012
    #5
  6. sinbad

    Eric Sosman Guest

    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
    d
    "The speed at which the system fails is usually not important."
    Eric Sosman, Sep 3, 2012
    #6
  7. sinbad

    Tim Rentsch Guest

    sinbad <> 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.
    Tim Rentsch, Sep 7, 2012
    #7
  8. sinbad

    Eric Sosman Guest

    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
    d
    "The speed at which the system fails is usually not important."
    Eric Sosman, Sep 10, 2012
    #8
  9. sinbad

    Tim Rentsch Guest

    Eric Sosman <> 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.
    Tim Rentsch, Dec 17, 2012
    #9
    1. Advertising

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

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Suzanne Vogel

    variable num args via 'va_arg'

    Suzanne Vogel, Jul 5, 2003, in forum: C++
    Replies:
    2
    Views:
    432
    flekso
    Jul 5, 2003
  2. Gordon Burditt

    Re: va_arg() question

    Gordon Burditt, Aug 7, 2003, in forum: C Programming
    Replies:
    0
    Views:
    399
    Gordon Burditt
    Aug 7, 2003
  3. Eric Sosman

    Re: va_arg() question

    Eric Sosman, Aug 7, 2003, in forum: C Programming
    Replies:
    1
    Views:
    488
    Kevin Easton
    Aug 8, 2003
  4. Artie Gold

    Re: va_arg() question

    Artie Gold, Aug 7, 2003, in forum: C Programming
    Replies:
    0
    Views:
    409
    Artie Gold
    Aug 7, 2003
  5. Mike Wahler

    Re: va_arg() question

    Mike Wahler, Aug 7, 2003, in forum: C Programming
    Replies:
    1
    Views:
    384
    Dave Thompson
    Aug 11, 2003
Loading...

Share This Page