[comp.lang.c] selectively enabling prototypes

friedl@mtndew.Tustin.CA.US (Steve Friedl) (08/12/90)

Hi folks,

     I like to use function prototypes when possible, so in my
header files I do something like this:

	#ifdef	USE_PROTO
	#  define	PROTO(name, args)	name args
	#else
	#  define	PROTO(name, args)	name ( )
	#endif

	extern PROTO(int printf, ( const char *, ... ) );
	extern PROTO(char *strcpy, ( char *, const char * ) );

and so on (note: I know that printf and strcpy are in other
headers, I'm just using familiar examples).  I have been doing
this for about two years and have been really happy with it.
However, I have seen it done "the hard way":

	#ifdef	USE_PROTO
	  extern int printf(const char *, ... );
	  extern char *strcpy(char *, const char * );
	#else
	  extern int printf()
	  extern char *strcpy();
	#endif

This looks like a real maintenance nightmare, but some of the
people who do it are people I respect, so I gotta wonder if they
know something that I don't know on this one.  Are there any
gotchas on doing it with the flavor of the way I've done it?

Note: I do know that things like signal() won't fit into my mold,
but these are so much in the minority that I don't mind doing those
few "the hard way".

     Steve

-- 
Stephen J. Friedl, KA8CMY / Software Consultant / Tustin, CA / 3B2-kind-of-guy
+1 714 544 6561  / friedl@mtndew.Tustin.CA.US  / {uunet,attmail}!mtndew!friedl

If Larry Ellison says it, it must be true.

daveb@llama.Ingres.COM (here kitty, kitty...) (08/14/90)

It's a mess.  The PROTO style of macro is convenient to write, but
violates one of the secret laws of C style: "Thou shalt not change the
syntax of the language with macros."  Using them messes up all sorts of
syntax-sensitive tools, and is confusing to the unitiated.

The solution of #ifdef around all the specific cases is bugly and a pain
to use, but it is correct and portable when done correctly.

Numberous people believe tha answer is really to write a filter that
turns ANSI code into Classic-C code, inserting all the casts into
function calls as needed.  This would be neet, but nobody knows of the
existance of such a program.  *sigh*  The person who writes one and
posts it will be *very* popular!

-dB

In <493@mtndew.Tustin.CA.US> friedl@mtndew.Tustin.CA.US (Steve Friedl) writes:
>Hi folks,
>
>     I like to use function prototypes when possible, so in my
>header files I do something like this:
>
>	#ifdef	USE_PROTO
>	#  define	PROTO(name, args)	name args
>	#else
>	#  define	PROTO(name, args)	name ( )
>	#endif
>
>	extern PROTO(int printf, ( const char *, ... ) );
>	extern PROTO(char *strcpy, ( char *, const char * ) );
>
>and so on (note: I know that printf and strcpy are in other
>headers, I'm just using familiar examples).  I have been doing
>this for about two years and have been really happy with it.
>However, I have seen it done "the hard way":
>
>	#ifdef	USE_PROTO
>	  extern int printf(const char *, ... );
>	  extern char *strcpy(char *, const char * );
>	#else
>	  extern int printf()
>	  extern char *strcpy();
>	#endif
>
>This looks like a real maintenance nightmare, but some of the
>people who do it are people I respect, so I gotta wonder if they
>know something that I don't know on this one.  Are there any
>gotchas on doing it with the flavor of the way I've done it?
>
>Note: I do know that things like signal() won't fit into my mold,
>but these are so much in the minority that I don't mind doing those
>few "the hard way".
>
>     Steve
>
>--
>Stephen J. Friedl, KA8CMY / Software Consultant / Tustin, CA / 3B2-kind-of-guy
>+1 714 544 6561  / friedl@mtndew.Tustin.CA.US  / {uunet,attmail}!mtndew!friedl
>
>If Larry Ellison says it, it must be true.

Ahem.
"Bottom of the 4th, Cooper pitching"	- tibetan baseball
David Brower:  daveb@rtech^H^H^H^H^Hingres.comd

karl@haddock.ima.isc.com (Karl Heuer) (08/16/90)

In article <1990Aug13.172452.14217@ingres.Ingres.COM> daveb@llama.Ingres.COM (here kitty, kitty...) writes:
>In <493@mtndew.Tustin.CA.US> friedl@mtndew.Tustin.CA.US (Steve Friedl) writes:
>>[I like to use:]
>>	#ifdef	USE_PROTO
>>	#  define	PROTO(name, args)	name args
>>	#else
>>	#  define	PROTO(name, args)	name ( )
>>	#endif
>>	extern PROTO(char *strcpy, ( char *, const char * ) );
>>However, I have seen it done "the hard way":
>>	#ifdef	USE_PROTO
>>	  extern char *strcpy(char *, const char * );
>>	#else
>>	  extern char *strcpy();
>>	#endif
>>This looks like a real maintenance nightmare...

Interesting; I would have said that the *former* method looks nightmarish.

>Numberous people believe tha answer is really to write a filter that
>turns ANSI code into Classic-C code,

That's what I recommend, and what I use for myself%.

>inserting all the casts into function calls as needed.  [But no such program
>exists.]

Wait a minute; now you've changed the problem.  The original two proposals
didn't handle the situation of mismatched argument types in a Classic C
environment, so it's unfair to make that a requirement.  The tool I use isn't
an ANSI-to-Classic converter, but it is a sufficiently powerful deprotoizer
for my purposes.

Karl W. Z. Heuer (karl@kelp.ima.isc.com or ima!kelp!karl), The Walking Lint
________
% In .c files and in single-project .h files, anyway.  Global header files
  tend to be static, so I just write them once, using what Steve calls the
  "hard way", and then ignore them once they're installed.

rtm@christmas.UUCP (Richard Minner) (08/17/90)

In article <493@mtndew.Tustin.CA.US>, Steve Friedl writes:
>
>     I like to use function prototypes when possible, so...
>	#ifdef	USE_PROTO
>	#  define	PROTO(name, args)	name args
>	#else
>	#  define	PROTO(name, args)	name ( )
>	#endif
>...
> However, I have seen it done "the hard way":
>	#ifdef	USE_PROTO
>	  extern int printf(const char *, ... );
>...
> This looks like a real maintenance nightmare, but some of the
> people who do it are people I respect, so I gotta wonder if they
> know something that I don't know on this one.  Are there any
> gotchas on doing it with the flavor of the way I've done it?

First, one caution that some may still not be aware of (even
though it's covered in the FAQ, question 22 :-), which is, briefly,
that with Classic style function declarations and definitions,
args of type char, short and float are (silently) promoted to int
and double, whereas with ANSI style they are not, e.g:

	extern int foo(char c);
	...
	int foo(c)
	char c;

doesn't work.  Those who don't understand should read FAQ, Q #22
(or thereabouts).

So you can't use your PROTO() macro with functions that take
char's, short's or float's as arguments.  Other than that, it
*works* fine (see end of article).  In fact, the "updated version
of the `Indian Hill C Style and Coding Standards'" (which was
posted here a while back, and by the way, thanks!), includes a
`PROTO()' macro similar to yours, like this:

   #ifdef HAVE_ANSI_PROTOTYPES
   #   define PROTO(args)	args
   #else
   #   define PROTO(args)	()
   #endif

I find this much cleaner, because the results look (to me) more
like normal declarations, and because it supports things like
signal() (and I use a fair number of `functions taking functions
as arguments').  It takes advantage of the fact that the only
difference between ANSI prototypes and Classic decls is the
presence of the argument list.

I actually use `P_()', because it's much more cryptic and hard
to read ;-), so signal() would look like:

   extern void ( *signal P_((int sig, void (*func)(int))) ) P_((int));

which is admittedly a bit ugly with all the ()'s, but:

   #ifdef HAVE_ANSI_PROTOTYPES
   extern void (*signal (int sig, void (*func)(int))) (int);
   #else
   extern void (*signal())();
   #endif

isn't much of an improvement.

By the way, don't forget typedefs, e.g:

    typedef int (*func_type) P((int, double, const char *));

As to why some experienced programmers would use the `#if'
approach instead, it's probably because they don't like macros
that alter the syntax of C, which is *not* an unreasonable
position, mind you.  It's your code, you choose.  Just don't be
surprised if someone someday is irritated by it.  I can't imagine
any other reasons to prefer `#if'.

-- 
Richard Minner  || {uunet,sun,well}!island!rtm     (916) 736-1323 ||
                || Island Graphics Corporation     Sacramento, CA ||

daveb@ingres.com (When a problem comes along . . . you must whip it) (08/17/90)

Please pardon the lengthy inclusion.

In <17424@haddock.ima.isc.com> karl@kelp.ima.isc.com (Karl Heuer) writes:
>In article <1990Aug13.172452.14217@ingres.Ingres.COM> I write:
>>In <493@mtndew.Tustin.CA.US> friedl@mtndew.Tustin.CA.US (Steve Friedl) writes:
>>>[I like to use:]
>>>	#ifdef	USE_PROTO
>>>	#  define	PROTO(name, args)	name args
>>>	#else
>>>	#  define	PROTO(name, args)	name ( )
>>>	#endif
>>>	extern PROTO(char *strcpy, ( char *, const char * ) );
>>>However, I have seen it done "the hard way":
>>>	#ifdef	USE_PROTO
>>>	  extern char *strcpy(char *, const char * );
>>>	#else
>>>	  extern char *strcpy();
>>>	#endif
>>>This looks like a real maintenance nightmare...
>
>Interesting; I would have said that the *former* method looks nightmarish.

This is my conclusion as well; It defeats tags generators and any other
syntax dependant tool.

>>Numberous people believe tha answer is really to write a filter that
>>turns ANSI code into Classic-C code,
>
>That's what I recommend, and what I use for myself%.
>
>>inserting all the casts into function calls as needed.  [But no such program
>>exists.]
>
>Wait a minute; now you've changed the problem.  The original two proposals
>didn't handle the situation of mismatched argument types in a Classic C
>environment, so it's unfair to make that a requirement.  The tool I use isn't
>an ANSI-to-Classic converter, but it is a sufficiently powerful deprotoizer
>for my purposes.

Perhaps; I'm uneasy.  One statement of the problem is "how do I
reasonably write code for both old and new C without going nuts?"  I
think most people would prefer NOT to have to run lint over a program to
verify arg matches when they've gone to the trouble of adding prototypes
-- prototypes solve the problem completely.  But if your method does not
ensure that casts are inserted in function calls, you've made no advance
on ensuring the type-correctness of the old-C.  If you go for the "write
ansi and deprotoize" solution, you may have gone backwards, because you
may not even think to put casts in arg lists.  So, unless the deprotoize
tool does put in the casts, you need to run lint.  Once you're running
lint, you've lost some of the value-added by ANSI.

I'm still inclined to think you need to have casts inserted, and that
nothing short of that is reliable enough to trust.

My apologies to Karl for not having dug into my archives earlier and
finding his converter.  It does indeed do what he advertises, I'm just
not convinced it is enough.  Also see "comp.sources.misc v06i068: Lex
programme to convert pANS to Classic C" by Robert Lupton
(lupton@uhccux.uhcc.Hawaii.Edu), which does the same sort of thing. 
Karl's is ~172 lines, the other ~246.

Someone suggested by mail that the "unprotoize" hack to GCC does the job
I want.  (If it doesn't, it's a lot of baggage to be carrying around
compared to Karl's and Lupton's!)  Does anyone know where I can get a
copy to try?  I have gcc-1.37.1 handy.

thanks,
-dB
--
"In my opinion, most C programs should be indented 6 feet under."
David Brower: {amdahl, cpsc6a, mtxinu, sun}!rtech!daveb daveb@ingres.com

andrew@alice.UUCP (Andrew Hume) (08/19/90)

	if one adopts the attitude that prototypes are good and old C is
just for compatibility, one is drawn to the technique of
using prototypes only in the code and running an awk script to
produce old style declarations. this is simple to do if one adopts
conventions such as all prototypes on one line etc.