Velocity Reviews - Computer Hardware Reviews

Velocity Reviews > Newsgroups > Programming > C Programming > variadic without va_arg

Reply
Thread Tools

variadic without va_arg

 
 
Bill Pursell
Guest
Posts: n/a
 
      04-29-2006

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

 
Reply With Quote
 
 
 
 
Diomidis Spinellis
Guest
Posts: n/a
 
      04-29-2006
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,;
> 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
 
Reply With Quote
 
 
 
 
Michael Mair
Guest
Posts: n/a
 
      04-29-2006
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,;
> 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.
 
Reply With Quote
 
Bill Pursell
Guest
Posts: n/a
 
      04-29-2006

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

 
Reply With Quote
 
Diomidis Spinellis
Guest
Posts: n/a
 
      04-29-2006
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.
 
Reply With Quote
 
Jack Klein
Guest
Posts: n/a
 
      04-29-2006
On 29 Apr 2006 00:16:30 -0700, "Bill Pursell" <(E-Mail Removed)>
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,;
> 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/~a...FAQ-acllc.html
 
Reply With Quote
 
 
 
Reply

Thread Tools

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off
Trackbacks are On
Pingbacks are On
Refbacks are Off


Similar Threads
Thread Thread Starter Forum Replies Last Post
Call again a variadic function (... variable number of arguments)with same arguments that its variadic wrapper moreau.steve@gmail.com C Programming 3 12-31-2008 07:13 AM
variadic function calling variadic function goldfita@signalsguru.net C Programming 5 05-03-2006 05:23 PM
Using variadic functions within variadic functions pinkfloydhomer@gmail.com C Programming 2 02-27-2006 05:47 AM
Variadic functions calling variadic functions with the argument list, HLL bit shifts on LE processors Ross A. Finlayson C Programming 19 03-10-2005 03:57 AM
is casting a variadic function to a non-variadic one legal? Colin Walters C Programming 2 02-13-2004 10:55 PM



Advertisments