varargs's doubt

Discussion in 'C Programming' started by grid, Apr 5, 2005.

  1. grid

    grid Guest

    Hi,
    I have a function which takes a variable number of arguments.It then
    calls va_start macro to initialize the argument list.But here in my case
    I have a special builtin va_start provided by the compiler which I use
    for performance gains.Then the variable arguments are passed to another
    varargs function which then uses a builtin va_arg macro to get the
    arguments.
    Iam getting an error from the compiler in the called function when a
    local va_list variable is assigned to the passed va_list.Is this not
    defined by the standard ?

    I get the following error code :

    gs.c(749): warning #563: initialization with "{...}" expected for
    aggregate object
    va_list ap = arglist;
    ^

    gs.c(749): error: a value of type "struct __builtin_va_list_struct_type
    *" cannot be used to initialize an entity of type "struct
    __builtin_va_list_struct_type"
    va_list ap = arglist;
    ^

    compilation aborted for gs.c (code 2)


    To show you how the code is in the source.Apologize to not being able to
    produce a minimal compilable program exhibiting the problem.

    gsscanf(char * fmt,...){
    va_list ap;
    .
    .
    __builtin_stdarg_start((ap),fmt);
    ret = gsScanfV(fmt,ap);
    __builtin_va_end( ap );
    }

    gsScanfV(char *fmt,va_list arglist)
    {
    va_list ap = arglist;
    .
    .
    .
    __builtin_va_arg(ap,type);
    __builtin_va_arg(ap,type);
    .
    .

    }

    I could solve the above compilation error by changing the "va_list
    arglist " to "va_list ap" in the gsScanfV() function parameters and
    removing the assignmet line (va_list ap = arglist;).
    Why is that I am getting an error when I assign the passed va_list
    variable to a local va_list variable ? Is this not defined by the standard ?

    I would like to hear some inputs on what is defined and what undefined
    with reference to the above code snippet.


    TIA,
     
    grid, Apr 5, 2005
    #1
    1. Advertising

  2. grid

    grid Guest


    > I could solve the above compilation error by changing the "va_list
    > arglist " to "va_list ap" in the gsScanfV() function parameters and
    > removing the assignmet line (va_list ap = arglist;).


    I also got another way to solve the problem.Changed the called function
    as below :
    gsScanfV(char *fmt,va_list arglist)
    {
    va_list ap ;
    va_copy(arglist,ap);
    .
    .
    .
    __builtin_va_arg(ap,type);
    __builtin_va_arg(ap,type);
    .
    .

    }

    Iam not sure which one looks an elegant solution.Request people to
    somment on the same.

    Regards,
    Rohitash
     
    grid, Apr 5, 2005
    #2
    1. Advertising

  3. On Tue, 05 Apr 2005 11:45:57 +0530, grid
    <> wrote:

    > I have a function which takes a variable number of arguments.It then
    > calls va_start macro to initialize the argument list.But here in my case
    > I have a special builtin va_start provided by the compiler which I use
    > for performance gains.Then the variable arguments are passed to another
    > varargs function which then uses a builtin va_arg macro to get the
    > arguments.


    For a start, I am very dubious that there are any significant
    performance gains in doing that. If there were, the library
    implementors should have defined the macrose in terms of the builtins in
    the first place. Have you actually profiled the code and found a
    significant difference there?

    Try it with the standard ones, does that work? Are you including a
    special header to get that builtin functionality?

    > Iam getting an error from the compiler in the called function when a
    > local va_list variable is assigned to the passed va_list.Is this not
    > defined by the standard ?


    It doesn't seem to be. C99 says that you can pass it as an argument to
    a function, and to va_start/va_arg/va_end/va_copy, but the existence of
    va_copy() would seem to imply that just assigning it is not a defined
    behaviour (gcc/glibc seems to accept it, but that doesn't imply that
    anything else would).

    > I get the following error code :
    >
    > gs.c(749): warning #563: initialization with "{...}" expected for
    > aggregate object
    > va_list ap = arglist;
    > ^
    >
    > gs.c(749): error: a value of type "struct __builtin_va_list_struct_type
    > *" cannot be used to initialize an entity of type "struct
    > __builtin_va_list_struct_type"
    > va_list ap = arglist;
    > ^


    It looks as though the compiler explicitly excludes variables of type
    __builtin_va_list_struct_type from being assigned. It may be
    implemented as an array, for instance.

    Something you could do is look at the preprocessed output and search for
    __builtin_va_list_struct_type, see if that is defined anywhere.

    > I could solve the above compilation error by changing the "va_list
    > arglist " to "va_list ap" in the gsScanfV() function parameters and
    > removing the assignmet line (va_list ap = arglist;).


    So why don't you do that? You said above that you are concerned about
    speed, why do an extra assignment?

    Chris C
     
    Chris Croughton, Apr 5, 2005
    #3
  4. grid

    grid Guest

    > So why don't you do that? You said above that you are concerned about
    > speed, why do an extra assignment?


    Chris,its not a piece of code written by me and hence wanted to confirm
    if I am not treading on undefined land because of my changes.Iam not
    sure why the original author chose to have an extra assignment.

    Which one of the two solutions look graceful ?

    TIA,
     
    grid, Apr 5, 2005
    #4
  5. On Tue, 05 Apr 2005 20:03:11 +0530, grid
    <> wrote:

    >> So why don't you do that? You said above that you are concerned about
    >> speed, why do an extra assignment?

    >
    > Chris,its not a piece of code written by me and hence wanted to confirm
    > if I am not treading on undefined land because of my changes.Iam not
    > sure why the original author chose to have an extra assignment.
    >
    > Which one of the two solutions look graceful ?


    I would use the parameter directly, that's the way with least code (and
    therefore the least to go wrong). Having an extra variable prompts the
    question "why? was there some reason it was done that way?".

    If va_copy() exists in your implementation (it's a C99 feature not in
    C89) then by all means use that.

    (Although I'd love to know how they managed to implement it such that a
    va_list can be passed as a parameter but not assigned directly, that
    smacks of special coding...)

    Chris C
     
    Chris Croughton, Apr 5, 2005
    #5
  6. grid

    Chris Torek Guest

    >On Tue, 05 Apr 2005 11:45:57 +0530, grid
    > <> wrote:
    >> I have a function which takes a variable number of arguments.It then
    >> calls va_start macro to initialize the argument list.But here in my case
    >> I have a special builtin va_start provided by the compiler which I use
    >> for performance gains.


    It is not a "performance" item, but rather a correctness issue.
    Using the __builtin_* functions will do the right thing; trying
    to fake it out by hand will not, in some cases.

    In any case, the __builtin_* stuff is hidden behind macros, and
    you, as the programmer, should just use the advertised interface
    (va_start, va_arg, va_end, and in this case, nothing else).

    >>Then the variable arguments are passed to another varargs
    >>function which then uses a builtin va_arg macro to get the arguments.


    This is allowed, but *only* via this form:

    SOMETYPE like_printf(const char *fmt, va_list ap) {
    /* code that uses va_arg(ap, TYPE) */
    }

    (of course SOMETYPE can be any valid function-return type, the name
    of the function can be any valid function name, the "fmt" argument
    can be something else, and so on). The important point here is
    that "ap" is a formal parameter of type "va_list". If va_list is
    a typedef for an array type, C's peculiar treatment of arrays will
    automatically adjust it to be "pointer to first element of that
    array" instead (as is happening here).

    In article <>
    Chris Croughton <> wrote:
    >For a start, I am very dubious that there are any significant
    >performance gains in doing that. If there were, the library
    >implementors should have defined the macrose in terms of the builtins in
    >the first place.


    You are right, and they did. (I am gazing into my crystal ball and
    I see that "grid" is using a PowerPC or similar architecture.)

    >It doesn't seem to be. C99 says that you can pass it as an argument to
    >a function, and to va_start/va_arg/va_end/va_copy, but the existence of
    >va_copy() would seem to imply that just assigning it is not a defined
    >behaviour


    This is correct. In particular, on the machine he has, the contents
    of <stdarg.h> include something like this:

    typedef struct __builtin_va_list_struct_type va_list[1];

    struct __builtin_va_list_struct_type {
    int __ni; /* number of saved int regs */
    int __i[__VA_MAXI]; /* and their values */
    int __nf; /* number of saved FPU regs */
    float __f[__VA_MAXF]; /* and their values */
    int *__rest; /* other values (on stack) */
    };

    >(gcc/glibc seems to accept it, but that doesn't imply that
    >anything else would).


    The machine on which va_copy is a simple copy, by contrast, has this
    in its <stdarg.h>:

    typedef char *va_list;

    Note the (second) error message here:

    >> gs.c(749): warning #563: initialization with "{...}" expected for
    >> aggregate object
    >> va_list ap = arglist;
    >> gs.c(749): error: a value of type "struct __builtin_va_list_struct_type
    >> *" cannot be used to initialize an entity of type "struct
    >> __builtin_va_list_struct_type"
    >> va_list ap = arglist;
    >> ^


    Clearly "arglist" has type "pointer to struct ...", while "ap" needs
    an initializer of type "struct ...", enclosed in braces. Clearly
    "ap" is actually an array type, while "arglist" is a pointer type.
    How can "arglist" be a pointer type when it also uses "va_list"
    as its declaration? The answer lies in The Rule about arrays and
    pointers in C.

    Let me show the two lines in question one more time here:

    int gsScanfV(const char *fmt, va_list arglist) {
    va_list ap = arglist;
    ...
    }

    If va_list is a typedef for an array type (and it is), we now
    have the same thing we would get with:

    void f(char arg_s[1]) {
    char local_s[1] = arg_s;
    ...
    }

    Here arg_s has type "char *" (pointer to char), while local_s has
    type "char [1]" (array 1 of char). The initializer is invalid
    and a diagnostic must occur.

    >> I could solve the above compilation error by changing the "va_list
    >> arglist " to "va_list ap" in the gsScanfV() function parameters and
    >> removing the assignmet line (va_list ap = arglist;).


    This is a good solution *provided* it is OK to destroy the va_list
    object whose element-address has been passed (courtesy, again, of
    The Rule). Consider for instance:

    int message(const char *fmt, ...) {
    va_list ap;
    int ret;

    va_start(ap, fmt);
    ret = vfprintf(stdout, fmt, ap);
    va_end(ap);
    return ret;
    }

    This function works just like printf(). The vfprintf() function,
    which receives the address of the first (and only) element of the
    "struct" containing the various registers, is allowed to modify
    ap[0].__ni (AKA ap->__ni) and ap->__nf and ap->__rest. Not only
    is this allowed, it actually happens. Now suppose we decide that
    message() should not only print to stdout, but also to a message-file.
    So we try change message() to read:

    int message(const char *fmt, ...) {
    va_list ap;
    int ret;
    extern FILE *logfile;

    va_start(ap, fmt);
    ret = vfprintf(stdout, fmt, ap);
    ret = vfprintf(logfile, fmt, ap); /* OOPS! WRONG! BUG! */
    va_end(ap);
    return ret;
    }

    As before, vfprintf() -- the first call, to stdout -- modifies
    ap->__ni and ap->__nf and ap->__rest. The second call receives
    these modified values -- and prints junk.

    On another machine, where "va_list" is just a typedef for "char *",
    the first vfprintf() modifies a *copy* of (the entire value of)
    ap, instead of the (single) array element ap[0]. The second
    vfprintf() gets another new, fresh copy, and it all works.

    In other words, this bug-ridden modified message() function works
    on some machines, and fails on others -- just what one can expect
    of code with undefined behavior.

    One fix, and the only one available in C89, is to rewrite
    message() itself:

    int message(const char *fmt, ...) {
    va_list ap;
    int ret;
    extern FILE *logfile;

    va_start(ap, fmt);
    ret = vfprintf(stdout, fmt, ap);
    va_end(ap);
    va_start(ap, fmt);
    ret = vfprintf(logfile, fmt, ap); /* OK this time */
    va_end(ap);
    return ret;
    }

    The first va_end cleans up, and the second va_start "resets" the
    array contents, so that the second vfprintf() works.

    This is all well and good until we go to write the missing vmessage()
    function, a la fprintf() and vfprintf(). Our mesage() has the
    actual "fmt, ..." parameters and can use va_start() twice -- but
    vmessage() should read something like:

    int vmessage(const char *fmt, va_list ap) {
    extern FILE *logfile;

    (void) vfprintf(stdout, fmt, ap);
    return vfprintf(logfile, fmt, ap); /* OOPS! WRONG! BUG! */
    }

    Here we have the same problem as last time: we destroy the values
    in ap[0].__ni, ap[0].__nf, and so on, so that the second vfprintf()
    prints junk. But in C89, there is no way to fix it. We can
    instead require two copies of the va_list:

    int ugly_vmessage(const char *fmt, va_list ap1, va_list ap2) {
    extern FILE *logfile;

    (void) vfprintf(stdout, fmt, ap1);
    return vfprintf(logfile, fmt, ap2); /* OK now */
    }

    which requires the caller to do two va_start()s. This is the only
    thing we *can* do. In C99, however, we have the new va_copy macro,
    so we can fix it the other way:

    int vmessage(const char *fmt, va_list ap) {
    extern FILE *logfile;
    va_list copy;

    va_copy(copy, ap);
    (void) vfprintf(stdout, fmt, copy);
    va_end(copy); /* see note below */

    return vfprintf(logfile, fmt, ap); /* OK now */
    }

    Now vmessage() still destroys its "va_list" argument on the way
    out, but it first makes a copy and uses that to print to stdout.
    Note, my C99 draft standard implies that the above va_end() call
    is required -- that a va_copy() has a sort of implied va_start()
    -- although this is just from an example and not from the text
    itself. I would check the final standard before assuming this is
    correct.

    Without seeing all the code for gsScanfV() and its caller(s),
    I cannot say whether a va_copy() is required. (My crystal ball
    is a bit cloudy.)
    --
    In-Real-Life: Chris Torek, Wind River Systems
    Salt Lake City, UT, USA (40°39.22'N, 111°50.29'W) +1 801 277 2603
    email: forget about it http://web.torek.net/torek/index.html
    Reading email is like searching for food in the garbage, thanks to spammers.
     
    Chris Torek, Apr 5, 2005
    #6
  7. On 5 Apr 2005 17:25:21 GMT, Chris Torek
    <> wrote:

    > In article <>
    > Chris Croughton <> wrote:


    >>It doesn't seem to be. C99 says that you can pass it as an argument to
    >>a function, and to va_start/va_arg/va_end/va_copy, but the existence of
    >>va_copy() would seem to imply that just assigning it is not a defined
    >>behaviour

    >
    > This is correct. In particular, on the machine he has, the contents
    > of <stdarg.h> include something like this:
    >
    > typedef struct __builtin_va_list_struct_type va_list[1];
    >
    > struct __builtin_va_list_struct_type {
    > int __ni; /* number of saved int regs */
    > int __i[__VA_MAXI]; /* and their values */
    > int __nf; /* number of saved FPU regs */
    > float __f[__VA_MAXF]; /* and their values */
    > int *__rest; /* other values (on stack) */
    > };


    Aha! Yes, that explains why assignment doesn't work. So would

    typedef unsigned char va_list[128];

    for that matter.

    >>(gcc/glibc seems to accept it, but that doesn't imply that
    >>anything else would).

    >
    > The machine on which va_copy is a simple copy, by contrast, has this
    > in its <stdarg.h>:
    >
    > typedef char *va_list;


    Yes, that (or similar with void*) is the first implementation of varargs
    I encountered, many years ago...

    >>> I could solve the above compilation error by changing the "va_list
    >>> arglist " to "va_list ap" in the gsScanfV() function parameters and
    >>> removing the assignmet line (va_list ap = arglist;).

    >
    > This is a good solution *provided* it is OK to destroy the va_list
    > object whose element-address has been passed (courtesy, again, of
    > The Rule). Consider for instance:


    [big snip describing why va_copy is needed]

    Thanks! I've never seen such a comprehensive (and comprehensible)
    description of the problem and solution before.

    > Without seeing all the code for gsScanfV() and its caller(s),
    > I cannot say whether a va_copy() is required. (My crystal ball
    > is a bit cloudy.)


    It's a pity va_copy() wasn't in C89...

    Thanks for the explanations...

    Chris C
     
    Chris Croughton, Apr 6, 2005
    #7
  8. grid

    grid Guest

    > This is correct. In particular, on the machine he has, the contents
    > of <stdarg.h> include something like this:
    >
    > typedef struct __builtin_va_list_struct_type va_list[1];
    >
    > struct __builtin_va_list_struct_type {
    > int __ni; /* number of saved int regs */
    > int __i[__VA_MAXI]; /* and their values */
    > int __nf; /* number of saved FPU regs */
    > float __f[__VA_MAXF]; /* and their values */
    > int *__rest; /* other values (on stack) */
    > };
    >
    >>(gcc/glibc seems to accept it, but that doesn't imply that
    >>anything else would).

    >


    I tried to see the <stdarg.h> file for my implementation (its glibc on
    x86_64 for Linux ),and I dont seem to find the builtin_va_list_struct_type.
    So I wrote a simple program,and I am pasting the preprocessed output.The
    <stdarg.h> for my implementation is similar to this
    http://www.elrincondelprogramador.com/articulos/docs/54/stdarg.h .

    Program :
    --------
    #include<stdio.h>
    #include<stdarg.h>

    void myprintf(const char *fmt,...)
    {
    va_list ap;
    int a,b;

    va_start(ap,fmt);
    a = va_arg(ap,int);
    b = va_arg(ap,int);
    va_end(ap);
    printf("a == %d \n b == %d \n",a ,b);
    }

    int main()
    {
    myprintf("%d %d\n",1,2);
    return 0;
    }

    Preprocessed Output (Edited the irrelevent parts ):
    ---------------------------------------------------

    # 1 "/usr/lib/gcc-lib/x86_64-redhat-linux/3.2.3/include/stdarg.h" 1 3
    # 43 "/usr/lib/gcc-lib/x86_64-redhat-linux/3.2.3/include/stdarg.h" 3
    typedef __builtin_va_list __gnuc_va_list;

    # 2 "test.c" 2
    # 1 "/usr/lib/gcc-lib/x86_64-redhat-linux/3.2.3/include/stdarg.h" 1 3
    # 110 "/usr/lib/gcc-lib/x86_64-redhat-linux/3.2.3/include/stdarg.h" 3
    typedef __gnuc_va_list va_list;
    # 3 "test.c" 2

    void myprintf(const char *fmt,...)
    {
    va_list ap;
    int a,b;
    __builtin_stdarg_start((ap),fmt);
    a = __builtin_va_arg(ap,int);
    b = __builtin_va_arg(ap,int);
    __builtin_va_end(ap);
    printf("a == %d \n b == %d \n",a ,b);

    }
    int main()
    {
    myprintf("%d %d\n",1,2);
    return 0;
    }

    I am just invoking usin the default options, and hence should not invoke
    with -C99 flags , moreover programs are here are not compiled with
    -C99 flag as yet.So probably the implementation provides a va_copy
    implementation for the C89 mode too.The compiler is gcc version 3.2.3
    20030502 (Red Hat Linux 3.2.3-34) for the RHEL.

    I would like to see the actual implementations of the __builtin_* types
    , but could not find in the include dir.

    Thanks,
     
    grid, Apr 6, 2005
    #8
  9. On Wed, 06 Apr 2005 19:22:22 +0530, grid
    <> wrote:

    > I tried to see the <stdarg.h> file for my implementation (its glibc on
    > x86_64 for Linux ),and I dont seem to find the builtin_va_list_struct_type.


    If it's builtin it probably means that it's a type the compiler knows
    about without any headers (like it does int, double etc.). As it
    happens I've just build GCC 3.4.3 so I have the source tree lying
    around...

    > So I wrote a simple program,and I am pasting the preprocessed output.The
    > <stdarg.h> for my implementation is similar to this
    > http://www.elrincondelprogramador.com/articulos/docs/54/stdarg.h .
    >
    > Preprocessed Output (Edited the irrelevent parts ):
    > ---------------------------------------------------
    >
    > # 1 "/usr/lib/gcc-lib/x86_64-redhat-linux/3.2.3/include/stdarg.h" 1 3
    > # 43 "/usr/lib/gcc-lib/x86_64-redhat-linux/3.2.3/include/stdarg.h" 3
    > typedef __builtin_va_list __gnuc_va_list;


    There it is. On this system it seems to be __builtin_va_list instead.
    Looking at the GCC Source I find that it is indeed recognised as an
    internal type (I'm not going to dig and work out what exactly).

    > I would like to see the actual implementations of the __builtin_* types
    > , but could not find in the include dir.


    You won't find them in the headers, they are internal to the compiler.
    You could download the compiler source (go for gcc-core, it's a lot
    smaller than the whole thing) and try to work out what that is doing...

    The point is that the internals of such macros are only of interest
    academically. If you try to use such undocumented and implementation
    specific things then you will run into trouble as soon as the
    implementers change them, which they are quite likely to do. And being
    macros you won't gain anything by expanding them by hand anyway...

    Chris C
     
    Chris Croughton, Apr 6, 2005
    #9
  10. grid

    Chris Torek Guest

    [I wrote, in part:]
    >> This is correct. In particular, on the machine he has, the contents
    >> of <stdarg.h> include something like this:
    >>
    >> typedef struct __builtin_va_list_struct_type va_list[1];
    >>
    >> struct __builtin_va_list_struct_type {
    >> int __ni; /* number of saved int regs */
    >> int __i[__VA_MAXI]; /* and their values */
    >> int __nf; /* number of saved FPU regs */
    >> float __f[__VA_MAXF]; /* and their values */
    >> int *__rest; /* other values (on stack) */
    >> };


    In article <q1S4e.38$>
    grid <> wrote:
    >I tried to see the <stdarg.h> file for my implementation (its glibc on
    >x86_64 for Linux), and I dont seem to find the builtin_va_list_struct_type.


    Aha. I thought you probably had some other compiler, and were
    most likely on a PowerPC-like machine. (Just goes to show that
    my crystal ball is cloudier than I would like. :) )

    >So I wrote a simple program, and I am pasting the preprocessed output. ...


    You will not find the expansion of the structure here. The reason
    is simple enough: the GNU GCC folks have done the sensible thing,
    and put the important contents of <stdarg.h> -- which must match
    the compiler's code for __builtin_stdarg_start, and is therefore
    known to the compiler -- inside the compiler. (See how neat it
    is? All the magic is inside the compiler: the definition of the
    structure itself, the code that fills it in, and the code that uses
    it, are all in the same place, and hence cannot get out of sync
    with each other.)

    Unfortunately (?), this has the side effect that the magic is now
    invisible to the casual programmer. The only place to see it all
    happen is in the source code to the compiler.

    >I would like to see the actual implementations of the __builtin_*
    >types, but could not find in the include dir.


    Alas, they are off-topic here, and the on-topic groups (gnu.gcc.*)
    are mostly dead on my news server (gnu.g++.bug gets a lot of spam
    and an occasional actual bug report).
    --
    In-Real-Life: Chris Torek, Wind River Systems
    Salt Lake City, UT, USA (40°39.22'N, 111°50.29'W) +1 801 277 2603
    email: forget about it http://web.torek.net/torek/index.html
    Reading email is like searching for food in the garbage, thanks to spammers.
     
    Chris Torek, Apr 8, 2005
    #10
    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. Chris Uppal

    Varargs

    Chris Uppal, Jan 19, 2005, in forum: Java
    Replies:
    4
    Views:
    518
    Chris Uppal
    Jan 20, 2005
  2. Bob Nelson

    doubt about doubt

    Bob Nelson, Jul 28, 2006, in forum: C Programming
    Replies:
    11
    Views:
    635
  3. Replies:
    0
    Views:
    570
  4. Peter Otten
    Replies:
    2
    Views:
    125
    Cousin Stanley
    Aug 10, 2013
  5. Terry Reedy
    Replies:
    0
    Views:
    120
    Terry Reedy
    Aug 10, 2013
Loading...

Share This Page