ghoti+@andrew.cmu.edu (Adam Stoller) (06/30/90)
(lengthy - but lot's of white-space -- sorry) Some of this is old news, but some may not be - and I'm a bit interested in some [reasonable] opinions on either.... [prolog (harmonicas playing in the background?)] As many of us are aware - there is a wonderful thing called prototyping -- especially with respect to ANSI-C. Unfortunately we are also aware that while some people on some platforms may have full prototyping abilities others may not -- and this tends to cause problems for people who'd like to try and make their code portable.... There are two different places where this comes into effect: 1) extern reference declarations 2) function definitions The first I believe has been reasonably well addressed. For those not familliar with a way to easily handle it I provide the following [possible] definition: /* external reference declarations */ /* use: extern int foo P_((int x, char c, long d)); */ #ifdef __STDC__ #define P_(args) args #else #define P_(args) () #endif A hack? Yes, but a farily clean and reasonable one (IMO). The second situation however I haven't seen addressed much. About the only way I've seen people deal with it was by doing something like: int #ifdef __STDC__ foo(int x, char c, long d) #else foo(x, c, d) int x; char c; long d; #endif { .... } This, IMO, is both a painful (to implement/maintain) and ugly hack - and one which I'd like to avoid if possible. To this end - one of the people I work with came up with the idea for a macro to use for the definitions - which I'd like to share with you now and get some opinions on: #ifdef __STDC__ #define PP_(a) ( #define V_ void) #define S_ , #define E_ ) #else #define PP_(a) a #define V_ #define S_ ; #define E_ ; #endif This allows the above example to be defined like: int foo PP_((x, c, d)) int x S_ char c S_ long d E_ { ...... } Or int foo PP_(()) V_ { .... } Which for the most part is reasonably close to the old-style definitions though nevertheless hackish. [Question Time] Q-1) Is it too ugly for words ? Personally, I admit not liking the loss of the commas/semicolons which are pretty much second-nature when typing in the code, but other than that it doesn't bother me too much. Q-2) Is it too hackish to be considered reasonable ? I think it stays within the boundary of reasonable hacks (whatever that boundary may be, unless you're a purist who considers any hack evil??) Q-3) Is it reasonably maintainable ? It suffers a little of the same problem as the earlier #ifdef example - in that there are still two places where the name and number of arguments must be maintained - but I think this one wears a little better in that it's really only as bad(?) as the old-style definitions. Q-4) Can it be made better ? Mostly I mean this in terms of the symbols chosen for the macros, and possibly the macros themselves. Certainly there are tons of alternatives ("use A_ instead of S_", etc) - but I'm looking for something a bit more substantial, with reasonable explanation. (hey, it's always possible that we might have gotten it "right" the first time....:-) (FYI: V_ == void, S_ == separator, E_ == end-of-list) [Visualization] (some blank lines removed from preprocessor output) [ | <-column 0] ---------------- > cat foo.c /* #define's as shown above */ int fname PP_((a1, a2)) int a1 S_ int a2 E_ { } struct lconv *ansi_localeconv PP_(()) V_ { } ---------------- > cc -E -D__STDC__ foo.c int fname ( int a1 , int a2 ) { } struct lconv *ansi_localeconv ( void) { } ---------------- > cc -E -U__STDC__ foo.c int fname (a1, a2) int a1 ; int a2 ; { } struct lconv *ansi_localeconv () { } ----------------
ghoti+@andrew.cmu.edu (Adam Stoller) (07/01/90)
In reading my message, now posted, I'd like to appologize for some of the formatting - Iforgot to make sure it would appear indented as attended (long story - don't ask ;-) However, I also think I found a situation not covered under my examples - that is, varargs (...) - so add the following to the defines: #ifdef __STDC__ #define D_ ... #else #define D_ /* or should that be #define D_ va_list ?? or something?? */ #endif As you can see, this raises a few more questions - again [reasonable] responses appreciated. Thanks --fish
karl@haddock.ima.isc.com (Karl Heuer) (07/04/90)
[Note: To conserve vertical space I've paraphrased heavily and eliminated some newlines from C code examples; the style questions herein deal with the use of the macros, not the whitespace convention. --kwzh] In article <MaWxonu00as9AA2kM1@andrew.cmu.edu> ghoti+@andrew.cmu.edu (Adam Stoller) writes: >[Prototypes are good, but it's also good to have code that's sufficiently >portable that it can be compiled on a pre-ANSI implementation of C.] >About the only way I've seen people deal with it was by doing something like: >[#1] > #ifdef __STDC__ > foo(int x, char c, long d) > #else > foo(x, c, d) int x; char c; long d; > #endif > >[#2: A proposed solution using a bunch of macros that expand into the correct >keywords and punctuators in the two C universes:] > int foo PP_((x, c, d)) int x S_ char c S_ long d E_ { ... } Yuck. In my opinion, #2 is worse than #1; the improvement in maintainability by having only one set of declarations is more than offset by the pain of having to use what is, in effect, a new language. Consider instead... #3: Use prototypes only for declarations, and continue to use the obsolescent syntax for definitions (but with a declaring prototype, preferably in a header, already in scope). #ifdef __STDC__ extern int foo(int, int, long); #endif int foo(x, c, d) int x; int c; long d; { ... } Since Classic C can't pass `char' by value, the middle argument should actually be declared `int' to minimize surprises. #4: Just use prototypes. On a system with no ANSI compiler, use deproto <foo.c >temp.c; cc -c temp.c; mv temp.o foo.o; rm temp.c or something equivalent. (This can be tucked into a makefile, or a shell script that emulates `cc'.) This is the approach that I use myself; it also has the advantage that, when ANSI is ubiquitous and nobody cares about Classic C anymore, the maintenance programmer won't have to do a thing. The drawback, of course, is that you have to have a deprotoizer. One such is `unprotoize', which hangs off of gcc. My preference is to maintain one that is entirely self-contained (it doesn't even require lex), and is short enough that you could type it in from the hardcopy listing if you had to. The bad news is that, in order to meet this latter requirement, the language it accepts is trimmed to the bone. It doesn't handle arguments of type array or function, or pointers to such, but the former are useless and the latter are too rare to be worth the trouble. A more serious problem is that it's tuned to my personal coding style. I hope to get back to it someday and make it less picky about horizontal whitespace at least, but for now it's provided as-is. (Send me any improvements, but keep the program small.) There's a compile-time debugging option that will make it indicate at what point the attempted parse failed on each line. Karl W. Z. Heuer (karl@kelp.ima.isc.com or ima!kelp!karl), The Walking Lint --------cut here-------- /* * deproto 1.00 by Karl Heuer. public domain. * * expects a prototype to match one of these two patterns: * decl ::= \t \q*\i(\t` \q+'?`, \t` \q+'?'*`, ...'?); * defn ::= \t \q*\i(`void|\t \q*\i`, \t \q*\i'*'`, ...'?) { * `' denotes grouping, | alternation, * Kleene closure, + positive closure, * ? option, \i identifier, \t type (single word or known multi-word type, * optionally preceded by a storage class); \q type qualifier or literal star; * other characters literal. examples: * int main(int argc, char **argv) { * extern int rand(void); * static int myprintf(char const *fmt, ...) { */ #include <stdio.h> #include <string.h> #define MAXLINE 2048 #define MAXARGS 32 static char buf[MAXLINE]; static char *larg[MAXARGS], *marg[MAXARGS], *rarg[MAXARGS]; #define isword(c) (c>='a'&&c<='z'||c>='A'&&c<='Z'||c>='0'&&c<='9'||c=='_') #if defined(DEBUG) #define doskip(k) { fprintf(stderr, "%d <%.*s|%.*s>\n", k, p-buf, buf, strlen(p)-1, p); goto skip; } #else #define doskip(ignore) goto skip #endif static int skiptype(pp) register char **pp; { #define p (*pp) static char *known[] = { "short int", "long int", "unsigned short int", "unsigned long int", "unsigned int", "unsigned char", "unsigned short", "unsigned long", }; register char **k; for (k = known; k < known+sizeof(known)/sizeof(char *); ++k) { register int len = strlen(*k); if (strncmp(p, *k, len) == 0 && !isword(p[len])) { p += len; return (1); } } if (strncmp(p, "struct ", 7) == 0) p += 7; else if (strncmp(p, "union ", 6) == 0) p += 6; else if (strncmp(p, "enum ", 5) == 0) p += 5; if (!isword(*p)) return (0); do ++p; while (isword(*p)); return (1); #undef p } static char *skipstar(p) register char *p; { for (;;) { if (*p == '*') ++p; else if (strncmp(p, "const ", 6) == 0) p += 6; else if (strncmp(p, "volatile ", 9) == 0) p += 9; else return (p); } } main() { char *p; register int i, n; register int isdecl, isdefn; while (fgets(buf, MAXLINE, stdin) != NULL) { p = buf; if (strncmp(p, "extern ", 7)==0 || strncmp(p, "static ", 7)==0) p += 7; if (!skiptype(&p)) doskip(0); if (*p++ != ' ') doskip(1); p = skipstar(p); if (!isword(*p)) doskip(2); do ++p; while (isword(*p)); if (*p++ != '(') doskip(3); n = 0; isdecl = isdefn = 1; for (;;) { larg[n] = p; if (strncmp(p, "register ", 9) == 0) p += 9; if (!skiptype(&p)) doskip(4); if (*p != ' ') { isdefn = 0; } else { ++p; p = skipstar(p); if (!isword(*p)) isdefn = 0; } marg[n] = p; while (isword(*p)) ++p; rarg[n++] = p; if (*p != ',' || p[1] != ' ') break; p += 2; if (*p == '.' && p[1] == '.' && p[2] == '.') { p += 3; break; } } if (*p != ')') doskip(5); if (isdecl && (p[1] == ';' || (p[1] == ' ' && p[2] == '{' && strncmp(larg[0], "void)", 5) == 0))) { printf("%.*s%s", larg[0]-buf, buf, p); } else if (p[1] == ' ' && p[2] == '{' && isdefn) { printf("%.*s", larg[0]-buf, buf); for (i=0; i<n; ++i) { if (i != 0) printf(", "); printf("%.*s", rarg[i]-marg[i], marg[i]); } printf(") "); for (i=0; i<n; ++i) printf("%.*s; ", rarg[i]-larg[i], larg[i]); printf("%s", p+2); } else { skip: printf("%s", buf); } } exit(0); }
ghoti+@andrew.cmu.edu (Adam Stoller) (07/05/90)
Excerpts from netnews.comp.lang.c: 3-Jul-90 Re: Prototyping in an
imper.. Karl Heuer@haddock.ima.i (6245)
> Yuck.
In looking at it again - I agree - but for me it's a definite Yuck to
both of the options.
However, I apparently went down this road of hackdom due to a
brain-damaged compiler which whines incessantly when you mix prototype
declarations and old-style definitions (the compiler defines __STDC__
but apparently does not conform [completely] to ANSI specs).
So - having traveled the bumpy road, I come full circle - and will
probably choose to just a simple prototype declaration (which even if
not used, serves as additional documentation for the function) and
old-style definitions - at least until ANSI compliant
pre-processors/compilers are more prevelant than they are now.
Sorry to have poured such a filthy hack onto your collective screens (;-)
--fish