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