variadic without va_arg

Discussion in 'C Programming' started by Bill Pursell, Apr 29, 2006.

  1. Bill Pursell

    Bill Pursell Guest

    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;
    }
    }
     
    Bill Pursell, Apr 29, 2006
    #1
    1. Advertising

  2. Bill Pursell wrote:
    > 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;
    > }
    > }


    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)
    http://www.spinellis.gr/codequality?clc
     
    Diomidis Spinellis, Apr 29, 2006
    #2
    1. Advertising

  3. Bill Pursell

    Michael Mair Guest

    Bill Pursell schrieb:
    > 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;
    > }
    > }


    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.
     
    Michael Mair, Apr 29, 2006
    #3
  4. Bill Pursell

    Bill Pursell Guest

    Diomidis Spinellis wrote:
    > Bill Pursell wrote:
    > > 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;
    > > }
    > > }

    >
    > 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.


    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.
     
    Bill Pursell, Apr 29, 2006
    #4
  5. Bill Pursell wrote:
    > Diomidis Spinellis wrote:
    >> Bill Pursell wrote:
    >>> 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?


    [...]

    > 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.


    Still wrong; see Michael Mair's reply to your original post.
     
    Diomidis Spinellis, Apr 29, 2006
    #5
  6. Bill Pursell

    Jack Klein Guest

    On 29 Apr 2006 00:16:30 -0700, "Bill Pursell" <>
    wrote in comp.lang.c:

    >
    > 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;
    > }
    > }


    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.learn.c-c++
    http://www.contrib.andrew.cmu.edu/~ajo/docs/FAQ-acllc.html
     
    Jack Klein, Apr 29, 2006
    #6
    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:
    436
    flekso
    Jul 5, 2003
  2. Colin Walters
    Replies:
    2
    Views:
    526
    Ben Pfaff
    Feb 13, 2004
  3. Ross A. Finlayson
    Replies:
    19
    Views:
    604
    Keith Thompson
    Mar 10, 2005
  4. Replies:
    2
    Views:
    351
    Dave Thompson
    Feb 27, 2006
  5. Replies:
    5
    Views:
    368
Loading...

Share This Page