variadic without va_arg

Collapse
This topic is closed.
X
X
 
  • Time
  • Show
Clear All
new posts
  • Bill Pursell

    variadic without va_arg


    I don't particularly enjoy using the va_start macro family, and I've
    noticed that the following code works. I'm a little concerned about
    the fact that the prototypes for foo do not match. Is this safe?

    [tmp]$ cat q.c
    extern int foo(int x,...);

    int
    main (int argc, char const*const* argv) /*:)*/
    {
    foo(0);
    foo(1,8);
    foo(2,3,7);
    foo(4,1,2,3,4);
    return 0;
    }
    [tmp]$ cat r.c

    int
    foo(int num, int a, int b, int c)
    {
    switch(num) {
    case 0: return 0;
    case 1: return a;
    case 2: return a+b;
    case 3: return a+b+c;
    default: return -1;
    }
    }

  • Diomidis Spinellis

    #2
    Re: variadic without va_arg

    Bill Pursell wrote:[color=blue]
    > I don't particularly enjoy using the va_start macro family, and I've
    > noticed that the following code works. I'm a little concerned about
    > the fact that the prototypes for foo do not match. Is this safe?
    >
    > [tmp]$ cat q.c
    > extern int foo(int x,...);
    >
    > int
    > main (int argc, char const*const* argv) /*:)*/
    > {
    > foo(0);
    > foo(1,8);
    > foo(2,3,7);
    > foo(4,1,2,3,4);
    > return 0;
    > }
    > [tmp]$ cat r.c
    >
    > int
    > foo(int num, int a, int b, int c)
    > {
    > switch(num) {
    > case 0: return 0;
    > case 1: return a;
    > case 2: return a+b;
    > case 3: return a+b+c;
    > default: return -1;
    > }
    > }[/color]

    According to the C99 standard (section 6.9.1) "If a function that
    accepts a variable number of arguments is defined without a parameter
    type list that ends with the ellipsis notation, the behavior is undefined."

    Your code works, because your compiler and processor architecture use an
    argument passing convention compatible with the 1970s style C. At that
    time functions like printf were indeed called with fewer or more
    arguments than their specification and extra arguments or clever pointer
    arithmetic were used to access the remaining arguments. These tricks
    were however not portable, and prompted the development of the Unix
    vararg and later the ANSI stdarg facilities.

    Having said that I admit that processor architects and compiler vendors
    go to extreme lengths to make legacy code work. I tried your example on
    some architectures I thought it would bomb (a SPARC, an Itanium, and an
    Alpha), and it worked correctly. Still, there's no reason to write
    non-portable code: at the very least you demonstrate you're not playing
    by the rules. If I was reading your code I would worry that other
    problems might also be lurking in it.

    --
    Diomidis Spinellis
    Code Quality: The Open Source Perspective (Addison-Wesley 2006)

    Comment

    • Michael Mair

      #3
      Re: variadic without va_arg

      Bill Pursell schrieb:[color=blue]
      > I don't particularly enjoy using the va_start macro family, and I've
      > noticed that the following code works. I'm a little concerned about
      > the fact that the prototypes for foo do not match. Is this safe?
      >
      > [tmp]$ cat q.c
      > extern int foo(int x,...);
      >
      > int
      > main (int argc, char const*const* argv) /*:)*/
      > {
      > foo(0);
      > foo(1,8);
      > foo(2,3,7);
      > foo(4,1,2,3,4);
      > return 0;
      > }
      > [tmp]$ cat r.c
      >
      > int
      > foo(int num, int a, int b, int c)
      > {
      > switch(num) {
      > case 0: return 0;
      > case 1: return a;
      > case 2: return a+b;
      > case 3: return a+b+c;
      > default: return -1;
      > }
      > }[/color]

      No, this comes into conflict with C99, 6.7.5.3#9 and #15;
      you are using incompatible function types.
      You are invoking UB.

      Let us go for more practical reasons:
      - If you do not use int, then you might run into unpleasant
      surprises.
      int foo(int num, long a, short b, char c)
      with LONG_MAX > INT_MAX and sizeof long > sizeof int
      int foo(int num, long double a, double b, float c)
      both might give you trouble.
      - In addition, passing hundred arguments to foo() may be
      harmful in quite unexpected ways.
      - Another thing: You might have different calling conventions,
      maybe only for a certain number of parameters. Merriment ensues
      for fixed parameter order.
      - Your lint tool or linker warns you about it.

      If you really dislike variable argument list handling that much,
      then do not use variable argument lists -- you nearly always can
      roll an alternative avoiding them at some cost.


      Cheers
      Michael
      --
      E-Mail: Mine is an /at/ gmx /dot/ de address.

      Comment

      • Bill Pursell

        #4
        Re: variadic without va_arg


        Diomidis Spinellis wrote:[color=blue]
        > Bill Pursell wrote:[color=green]
        > > I don't particularly enjoy using the va_start macro family, and I've
        > > noticed that the following code works. I'm a little concerned about
        > > the fact that the prototypes for foo do not match. Is this safe?
        > >
        > > [tmp]$ cat q.c
        > > extern int foo(int x,...);
        > >
        > > int
        > > main (int argc, char const*const* argv) /*:)*/
        > > {
        > > foo(0);
        > > foo(1,8);
        > > foo(2,3,7);
        > > foo(4,1,2,3,4);
        > > return 0;
        > > }
        > > [tmp]$ cat r.c
        > >
        > > int
        > > foo(int num, int a, int b, int c)
        > > {
        > > switch(num) {
        > > case 0: return 0;
        > > case 1: return a;
        > > case 2: return a+b;
        > > case 3: return a+b+c;
        > > default: return -1;
        > > }
        > > }[/color]
        >
        > According to the C99 standard (section 6.9.1) "If a function that
        > accepts a variable number of arguments is defined without a parameter
        > type list that ends with the ellipsis notation, the behavior is undefined."
        >
        > Your code works, because your compiler and processor architecture use an
        > argument passing convention compatible with the 1970s style C. At that
        > time functions like printf were indeed called with fewer or more
        > arguments than their specification and extra arguments or clever pointer
        > arithmetic were used to access the remaining arguments. These tricks
        > were however not portable, and prompted the development of the Unix
        > vararg and later the ANSI stdarg facilities.
        >
        > Having said that I admit that processor architects and compiler vendors
        > go to extreme lengths to make legacy code work. I tried your example on
        > some architectures I thought it would bomb (a SPARC, an Itanium, and an
        > Alpha), and it worked correctly. Still, there's no reason to write
        > non-portable code: at the very least you demonstrate you're not playing
        > by the rules. If I was reading your code I would worry that other
        > problems might also be lurking in it.[/color]

        Would it be portable to simply change the prototypes so that the caller
        has the interface:
        extern int foo(int num, ...)
        while the definition of the function gets:
        int foo(int num, int a, int b, int c, ...)?

        That seems to satisfy the section of the standard you quote above, but
        it still feels wrong.

        Comment

        • Diomidis Spinellis

          #5
          Re: variadic without va_arg

          Bill Pursell wrote:[color=blue]
          > Diomidis Spinellis wrote:[color=green]
          >> Bill Pursell wrote:[color=darkred]
          >>> I don't particularly enjoy using the va_start macro family, and I've
          >>> noticed that the following code works. I'm a little concerned about
          >>> the fact that the prototypes for foo do not match. Is this safe?[/color][/color][/color]

          [...]
          [color=blue]
          > Would it be portable to simply change the prototypes so that the caller
          > has the interface:
          > extern int foo(int num, ...)
          > while the definition of the function gets:
          > int foo(int num, int a, int b, int c, ...)?
          >
          > That seems to satisfy the section of the standard you quote above, but
          > it still feels wrong.[/color]

          Still wrong; see Michael Mair's reply to your original post.

          Comment

          • Jack Klein

            #6
            Re: variadic without va_arg

            On 29 Apr 2006 00:16:30 -0700, "Bill Pursell" <bill.pursell@g mail.com>
            wrote in comp.lang.c:
            [color=blue]
            >
            > I don't particularly enjoy using the va_start macro family, and I've
            > noticed that the following code works. I'm a little concerned about
            > the fact that the prototypes for foo do not match. Is this safe?
            >
            > [tmp]$ cat q.c
            > extern int foo(int x,...);
            >
            > int
            > main (int argc, char const*const* argv) /*:)*/
            > {
            > foo(0);
            > foo(1,8);
            > foo(2,3,7);
            > foo(4,1,2,3,4);
            > return 0;
            > }
            > [tmp]$ cat r.c
            >
            > int
            > foo(int num, int a, int b, int c)
            > {
            > switch(num) {
            > case 0: return 0;
            > case 1: return a;
            > case 2: return a+b;
            > case 3: return a+b+c;
            > default: return -1;
            > }
            > }[/color]

            This only "works" as you want because your compiler uses a method
            required for pre-standard C for passing arguments. That may only be
            true with the particular set of compiler options that you use. If you
            change options, such as for optimization, it might well break.

            There are implementations where this will not work under any
            circumstances. They use completely different methods of passing
            arguments to variadic and non-variadic functions. I have used several
            such compilers over the years.

            --
            Jack Klein
            Home: http://JK-Technology.Com
            FAQs for
            comp.lang.c http://c-faq.com/
            comp.lang.c++ http://www.parashift.com/c++-faq-lite/
            alt.comp.lang.l earn.c-c++

            Comment

            Working...