Path: utzoo!attcan!uunet!jarthur!usc!apple!snorkelwacker!spdcc!ima!haddock!karl From: karl@haddock.ima.isc.com (Karl Heuer) Newsgroups: comp.lang.c Subject: Re: Prototyping in an imperfect world.... Message-ID: <17014@haddock.ima.isc.com> Date: 3 Jul 90 18:28:00 GMT References: Reply-To: karl@haddock.ima.isc.com (Karl Heuer) Organization: Interactive Systems, Cambridge, MA 02138-5302 Lines: 172 Supersedes: <17007@haddock.ima.isc.com> [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 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 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 #include #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