[comp.lang.c] ANSI/Non-ANSI Function Declaration Macros

peter@isgtec.UUCP (Peter Curran) (12/14/89)

This is a somewhat-modified re-post, due to technical difficulties.

In article <4603@itivax.iti.org> scs@itivax.iti.org (Steve Simmons) writes:
>
>Currently I'm writing code that must compile with and without ANSI
>C features.  Ideally this could be done with no slgging the code
>with lots of #ifdef constructs like
>
>#ifdef __STDC__
>int	*foo( int const a )
>#else
>int	*foo( a )
>int	a ;
>#endif
>{
>	func body
>}
>
>While this works, it's got a weakness.  In having two separate declarations,
>one can over time let them get out of sync.  It's also a pain to write.
>Has anybody come up with a way of doing this so that I write the declarations
>once and once only?  Bizarre constructs considered within limits.
>-- 

I have solved this problem in two parts.

1.  Define a symbol PROTOTYPES as 1 if your current compiler supports
	prototypes, 0 otherwise.  (You could base this on the existence
	and/or value of __STDC__, but we have seen in the last while
	that this isn't too reliable.)

2.  Define a macro P(x) as follows

#if PROTOTYPES
#	define P(x)		x
#else
#	define P(x)		()
#	endif

3.  Now you can declare functions as, for example

		int foo		P((FILE *file, int abc));

	Note the double parentheses - they are important.  If PROTOTYPES
	is 1, this preprocesses to

		int foo (FILE *file, int abc):

	Otherwise it preprocesses to

		int foo ();

	This is obviously uglier than using a real compiler, but only
	three characters extra - a lot better than millions of #if's.

3.  Write function headers in the old style:

int foo (file, abc)
FILE *file;
int abc;....

	This is obsolescent, but all standard-conforming compilers are
	required to support it, and presumably will for the foreseeable
	future.  This means I get the benefits of prototypes whenever
	possible, but can port to obsolete compilers without too much
	pain.  Of course, once UNIX catches up with the rest of the
	world, I will abandoned the obsolescent form entirely.

nfs@notecnirp.Princeton.EDU (Norbert Schlenker) (12/14/89)

In article <244@isgtec.UUCP| peter@isgtec.UUCP (Peter Curran) writes:
|...
|I have solved this problem in two parts.
|1.  Define a symbol PROTOTYPES as 1 if your current compiler supports
|	prototypes, 0 otherwise...
|2.  Define a macro P(x) as follows
|#if PROTOTYPES
|#	define P(x)		x
|#else
|#	define P(x)		()
|#	endif
|3.  Now you can declare functions as, for example
|		int foo		P((FILE *file, int abc));
|	Note the double parentheses - they are important.  If PROTOTYPES
|	is 1, this preprocesses to
|		int foo (FILE *file, int abc):
|	Otherwise it preprocesses to
|		int foo ();
|	This is obviously uglier than using a real compiler, but only
|	three characters extra - a lot better than millions of #if's.
|3.  Write function headers in the old style:
|
|int foo (file, abc)
|FILE *file;
|int abc;....
|
|	This is obsolescent, but all standard-conforming compilers are
|	required to support it, and presumably will for the foreseeable
|	future.  This means I get the benefits of prototypes whenever
|	possible, but can port to obsolete compilers without too much
|	pain.  Of course, once UNIX catches up with the rest of the
|	world, I will abandoned the obsolescent form entirely.

But there is a problem with this too!  With a K&R compiler, things work
just fine using this scheme.  But with an ANSI compiler, any function
taking a small argument (like char or short) will end up with a conflict
between the prototype and the actual file header.  For example:

extern void func(char c, short n);

void func(c, n)
char c;
short n;
{}

won't be accepted.  The K&R definition is going to be treated as if
it says:

void func(c, n)
int c;
int n;
{}

since argument widening is automatic in K&R.  Then there is a terrible
mismatch between the prototype and the definition, which every ANSI
compiler that I have seen complains bitterly about.  So now you need to
conditionally compile function headers, and that will always be less
pleasant since it can't be done automagically by the preprocessor.

peter@isgtec.UUCP (Peter Curran) (12/16/89)

In article <22402@princeton.Princeton.EDU> nfs@notecnirp.UUCP (Norbert Schlenker)
writes (in response to my suggesting for combining prototypes with the
obsolescent forms of functions, to port between ANSI and non-ANSI compilers):
|But there is a problem with this too!  With a K&R compiler, things work
|just fine using this scheme.  But with an ANSI compiler, any function
|taking a small argument (like char or short) will end up with a conflict
|between the prototype and the actual file header.  For example:
|
|extern void func(char c, short n);
|
|void func(c, n)
|char c;
|short n;
|{}
|
|won't be accepted.  The K&R definition is going to be treated as if
|it says:
|
|void func(c, n)
|int c;
|int n;
|{}
|
|since argument widening is automatic in K&R.  Then there is a terrible
|mismatch between the prototype and the definition, which every ANSI
|compiler that I have seen complains bitterly about.  So now you need to
|conditionally compile function headers, and that will always be less
|pleasant since it can't be done automagically by the preprocessor.

Not as far as I can see, unless I have badly mis-read the ANSI standard
(which is certainly possible :-))

First, I assume that, in ANSI, the following are exactly equivalent:

	void func (char c, short n)
	{...}
and
	void func (c, n)
	char c;
	short n;
	{...}

They are just different syntax for the same thing.  I would like to be
corrected if I have misunderstood this.

My prototype here would be

	void func P((char c, short n));


Assuming I am right, then the use of the obsolescent form of function
header is irrelevant (except that it allows the code to be compiled
with K&R compilers.)  ANSI compilers will see the prototype, and will
not widen the parameters; K&R compilers will not see the prototype
(which they can't handle), and will widen the parameters.  Assuming all
the code is compiled with the same compiler (i.e. both the function
and all calls to it), then widening will always be done, or it won't
be done.

I have ported a lot of code written this way, so I am pretty sure it
works.  I would love to abandon it, however... :-)

throopw@sheol.UUCP (Wayne Throop) (12/17/89)

> peter@isgtec.UUCP (Peter Curran)
> First, I assume that, in ANSI, the following are exactly equivalent:
>	void func (char c, short n) {...}
> and
>	void func (c, n) char c; short n; {...}
> They are just different syntax for the same thing.  I would like to be
> corrected if I have misunderstood this.

I am pretty sure that Peter has indeed misunderstood this.
A prototype declaration must be matched with a prototype definition.

I no longer have a copy of the standard directly to hand, but K&RII have
some things to say about it that make me quite sure.

> My prototype here would be    void func P((char c, short n));

In order to make the definitions and declarations agree, I use a more
complicated scheme involving the three macros PT, PF, and PP, standing
for "prototype", "protype function", and "prototype punctuation".  This
has been known to make people barf, though compilers seem to like it
just fine.  So, beware when reading on:

    return_type func PT((type1 arg1, type2 arg2, type3 arg3));

    ...

    return_type func PF((arg1, arg2, arg3),
                         type1 arg1 PP
                         type2 arg2 PP
                         type3 arg3 )
    { ... }

Now, P, PF and PP have the obvious definitions, which I'll give below.
They can be put in "protomac.h" and largely forgotten about.

This scheme does have the problem that you MUST have the arguments in
the correct order, and don't change your mind between the first
(typeless) and second (typed) list of arguments.  This is not horribly
more odious than bare K&R style declarations, so I live with it, and
await the day when K&R-only implementations are rare enough to dispense
with the charade. 

protomac.h:

    #ifndef PF
    #ifdef __STDC__
    #define PF(names,types) (types)
    #define PT(types) types
    #define PP ,
    #else
    #define PF(names,types) names types;
    #define PT(types) ()
    #define PP ;
    #endif
    #endif
--
Wayne Throop <backbone>!mcnc!rti!sheol!throopw or sheol!throopw@rti.rti.org

nfs@notecnirp.Princeton.EDU (Norbert Schlenker) (12/18/89)

In article <248@isgtec.UUCP> peter@isgtec.UUCP (Peter Curran) writes:
(in response to my demurral at his suggestion)
>In article <22402@princeton.Princeton.EDU> nfs@notecnirp.UUCP (Norbert Schlenker)
>writes (in response to my suggesting for combining prototypes with the
>obsolescent forms of functions, to port between ANSI and non-ANSI compilers):
>Not as far as I can see, unless I have badly mis-read the ANSI standard
>(which is certainly possible :-))
>
>First, I assume that, in ANSI, the following are exactly equivalent:
>
>	void func (char c, short n)
>	{...}
>and
>	void func (c, n)
>	char c;
>	short n;
>	{...}

Not so; see below.

>They are just different syntax for the same thing.  I would like to be
>corrected if I have misunderstood this.
>
>My prototype here would be
>
>	void func P((char c, short n));
>
>Assuming I am right, then the use of the obsolescent form of function
>header is irrelevant (except that it allows the code to be compiled
>with K&R compilers.)  ANSI compilers will see the prototype, and will
>not widen the parameters; K&R compilers will not see the prototype
>(which they can't handle), and will widen the parameters.  Assuming all
>the code is compiled with the same compiler (i.e. both the function
>and all calls to it), then widening will always be done, or it won't
>be done.

I don't have a copy of the standard handy here, so I can't quote
chapter and verse.  But in response to feeding the program:

  int func(char c, short s);

  int func(c, s)
  char c;
  short s;
  {
    return 0;
  }

into two (claimed) ANSI compilers, I get:

From gcc:
  test.c: In function func:
  test.c:6: argument `c' doesn't match function prototype
  test.c:6: a formal parameter type that promotes to `int'
  test.c:6: can match only `int' in the prototype
  test.c:6: argument `s' doesn't match function prototype
  test.c:6: a formal parameter type that promotes to `int'
  test.c:6: can match only `int' in the prototype

From lcc (a locally developed ANSI compiler):
  test.c:6: conflicting argument declarations for function `func'

The messages from gcc are rather more clear than those from lcc.  In both
cases, though, it is an error to specify something shorter than 'int' in
a prototype and then define it correspondingly in the actual function,
if that definition is of the old K&R type.

I believe, and the compilers believe, that the prototype which matches the
actual definition:

   int func(c, s)
   char c;
   short s;
   {}

must be:

   int func(int c, int s);

Note also that changing the actual function header from:

  int func(c, s)
  char c;
  short s;

to:

  int func(char c, short s)

makes the ANSI compilers happy (and of course annoys the K&R compilers).

Perhaps the language lawyers with quick access to X3J11 can verify this
with a section number.

Norbert

lmb@ghoti.uucp (Larry Breed) (12/19/89)

In article <248@isgtec.UUCP> peter@isgtec.UUCP (Peter Curran) writes:
>
>First, I assume that, in ANSI, the following are exactly equivalent:
>
[No. 1]
>	void func (char c, short n)
>	{...}
>and
[No. 2]
>	void func (c, n)
>	char c;
>	short n;
>	{...}

The answer is 'no', and it raises a point worth noting:  there is no Standard C
prototype declaration that portably matches the Classic C function in No. 2.

For the first definition, Standard C permits but does not require allocating only
a char-sized and a short-sized element in the argument list.  In fact many implementations
continue to widen such arguments and pass them in int-sized elements.

No. 2 is a Classic C function whose callers pass two int-sized arguments.  This *does not*
make it equivalent to 

[No. 3]
	void func (c, n)
	int c, n;
	{...}

In No. 2, c is truly a char.  Regardless of the width of the argument list
element, the value of c within the function definition must remain within the limits
for objects of char type.  Taking &c must produce a pointer to a char.  
It is true that callers would have to treat calls of nos. 2 and 3 identically,
in either a K&R or ANSI compiler environment.
Disclaimer: Don't blame my employer, blame:
Larry Breed			(415) 855-4460
uucp: uunet!ibmsupt!lmb		inet: ibmsupt!lmb@uunet.uu.net

karl@haddock.ima.isc.com (Karl Heuer) (12/21/89)

In article <0300@sheol.UUCP> throopw@sheol.UUCP (Wayne Throop) writes:
>I use a more complicated scheme involving the three macros...  This has been
>known to make people barf...

Barf.

Another alternative is to simply write the bloody thing using prototypes, and
then on pre-ANSI implementations, run it through a deprotoizing filter.  This
has the advantage that there's nothing to clean up once you decide that
supporting the absence of prototypes is as archaic as supporting the absence
of |unsigned char| or |void|.

One such deprotoizer can be obtained as a patch to gcc; I also maintain a much
shorter one that compares against my personal coding style rather than
everything that the language permits.

Karl W. Z. Heuer (ima!haddock!karl or karl@haddock.isc.com), The Walking Lint