g-rh@cca.CCA.COM (Richard Harter) (03/19/88)
In article <784@cresswell.quintus.UUCP> ok@quintus.UUCP (Richard A. O'Keefe) writes: ]In article <25652@cca.CCA.COM>, g-rh@cca.CCA.COM (Richard Harter) writes: ]> I don't know if this is what ANSI C expects, but it's not too hot if ]> it is, because we now have two descriptions of the calling sequence. ]> The right thing is simply ]> ]> #include "prototypes.h" ]> .... ]> type_of_foo foo(a,b) {....} ]> ]> In fact, even type_of_foo should be omitted, since that is really part ]> of the description. ]> ]It depends what you mean by "the right thing". ]If you mean "whatever means I have less to write", fine. ]If you mean "whatever makes C more like Pascal", fine. ]If you mean "whatever will make the code easier to maintain", ]you're dead wrong. I mean, of course, none of these things. I was referring to the ancient and venerable principle of programming that an entity should only be defined in one place. The reason for this is (my apologies for rehashing something you know very well) that if an entity is defined in more than one place and it is has to be changed than all instances have to be changed uniformly. Miss one, for whatever reason, and the code becomes inconsistent. The reason for saying that the above is "the right thing" to do is that the prototype (whether in a header file or in the code) need not match the declarations in called routine. The reason for this is simple -- you have specified the number and types of arguments in the calling sequence (a single entity) in several places. To be sure, if you use Lint or an equivalent tool, it will catch this. However Lint will catch this sort of thing even if you don't have prototypes. ]When as Joe Maintainer I come along and try to figure out what your function ]foo() does, I want to be able to see what the arguments and result are, ]which means that I want them *right* *there* with the rest of foo(), where ]I can see them. In fact, I want all the immediately useful information to ]be there where I don't have to hunt all over file space to find it. This is the other side of the coin -- there is another venerable principle that says that all relevent information should be immediately accessible. When you put things in a header file, they are no longer visible in the code. And this does slow down maintenance -- time spent looking things up (or worse yet, not looking things up and making incorrect assumptions) costs. There are various partial solutions. For example, you can replicate definitions and have the compiler check things. I.e. "prototypes.h" would contain a standard description, the calling routines contain prototypes (to provide information to the programmer) and the called routines contain declarations (again for information purposes). The compiler would then check consistency of specification(given in the header file) against the information given in the code. This leaves the maintenance problem (multiple specifications to alter) but at least ensure consistency. One can thing of other solutions. What they all have in common, as far as I can see, is that they stray a good deal of distance from the the way things are done now. As it stands, the real value of a prototype is informative -- the calling routine can contain a description of a calling sequence. Its value for protecting against erroneous usage of routines is nominal; lint is really much more useful. A prototype declaration does not protect you against an incorrect prototype declaration because it does not 'look' at the real master (the actual declaration in the code in the called routine) whereas lint does. -- In the fields of Hell where the grass grows high Are the graves of dreams allowed to die. Richard Harter, SMDS Inc.
ix426@sdcc6.ucsd.EDU (Tom Stockfisch) (03/19/88)
In article <25699@cca.CCA.COM> g-rh@CCA.CCA.COM.UUCP (Richard Harter) writes: > > As it stands, the real value of a prototype is informative -- >the calling routine can contain a description of a calling sequence. >Its value for protecting against erroneous usage of routines is nominal; >lint is really much more useful. A prototype declaration does not protect >you against an incorrect prototype declaration because it does not 'look' >at the real master (the actual declaration in the code in the called routine) >whereas lint does. The prototype DOES protect you against incorrect declaration if you put it in a header file that is included in BOTH the caller function file AND the callee function file. The compiler will then complain of an incompatable re-declaration if the header file is out of sync with the definition. This is much preferable to re-linting after every change to a prototype. It avoids the quadratic behavior of the following sequence: vi f1.c lint f1.c cc f1.c vi f2.c lint f1.c f2.c cc f1.o f2.c vi f3.c lint f1.c f2.c f3.c cc f1.o f2.o f3.c ... If you cut out the lint, the only remaining quadratic portion is in the loader. Of course, you'll still want to run lint every great once in a while to check other things (like conversion of function pointers, or long identifier clashes, or unreachable code). -- || Tom Stockfisch, UCSD Chemistry tps@chem.ucsd.edu
ok@quintus.UUCP (Richard A. O'Keefe) (03/19/88)
In article <25699@cca.CCA.COM>, g-rh@cca.CCA.COM (Richard Harter) writes: > I mean, of course, none of these things. I was referring to > the ancient and venerable principle of programming that an entity should > only be defined in one place. The trouble is that this conflicts with another ancient and venerable principle of programming: declaration before use. The cleanest answer would seem to be to have a set of programming tools which let you construct a forest of entities (types, variables, functions) and generate vanilla C code on request. (Sort of an |R**n for C.) (Preprocessors don't *have* to generate unreadable code; some of the clearest Fortran I've ever seen came out of a preprocessor.)
g-rh@cca.CCA.COM (Richard Harter) (03/20/88)
In article <788@cresswell.quintus.UUCP> ok@quintus.UUCP (Richard A. O'Keefe) writes: >The cleanest answer would seem to be to have a set of programming tools >which let you construct a forest of entities (types, variables, functions) >and generate vanilla C code on request. (Sort of an |R**n for C.) >(Preprocessors don't *have* to generate unreadable code; some of the >clearest Fortran I've ever seen came out of a preprocessor.) This is an important point. One has to ask, which issues are to be solved in the language design and which are to be handled in the tool set. One of the claims for Ada is that it is not only an language, but also an environment. If you have a bare bones environment, then you want a strong tool set, and vice versa. It is probably a mistake to think that all problems can be handled in the language design. There, now you have the general principle. I've done the hard part. The rest of you can work out the grubby details. :-) -- In the fields of Hell where the grass grows high Are the graves of dreams allowed to die. Richard Harter, SMDS Inc.
g-rh@cca.CCA.COM (Richard Harter) (03/20/88)
In article <3644@sdcc6.ucsd.EDU> ix426@sdcc6.ucsd.edu.UUCP (Tom Stockfisch) writes: > >The prototype DOES protect you against incorrect declaration if you put it >in a header file that is included in BOTH the caller function file AND the >callee function file. The compiler will then complain of an incompatable >re-declaration if the header file is out of sync with the definition. Do I understand this correctly to mean that you may have a prototype in the header, and in the caller body proper, and a declaration in the callee, and that all is okay as long as every thing matches? I ask, in part, because I don't know. This does work both for protection and for information (visibility in the code of the declaration). There are the usual problems that the declarations must, in fact, be there, and that all make files contain all relevant dependencies. That, however, is another matter. >This is much preferable to re-linting after every change to a prototype. >It avoids the quadratic behavior of the following sequence: > > vi f1.c > lint f1.c > cc f1.c > > vi f2.c > lint f1.c f2.c > cc f1.o f2.c > > vi f3.c > lint f1.c f2.c f3.c > cc f1.o f2.o f3.c Er, that does seem like an odd way to do things. If I have ten files to alter, I would edit all ten files first, lint them together, and compile them together. Or, if the change were complex in the individual files perhaps vi prototypes.h vi f1.c <--| (Edit and lint until f1.c passes preliminary lint f1.c ---| lint testing to remove trivial errors and typos) Repeat above loop for compiles, until compiler does not complain Repeat above process for all files Lint run when all files are processed. Link and initiate testing. I may be missing something, but I do not see the point of linking corrected and uncorrected code as a matter of routine policy. >If you cut out the lint, the only remaining quadratic portion is in the loader. >Of course, you'll still want to run lint every great once in a while to check >other things (like conversion of function pointers, or long identifier clashes, >or unreachable code). Well, I find it useful to run lint on individual files when I make big changes to them -- unfortunately I sometimes make typos, mistakes about types, and other errors like that. I like to catch as many of these things before I start serious checking. Other people may not need to. -- In the fields of Hell where the grass grows high Are the graves of dreams allowed to die. Richard Harter, SMDS Inc.
pablo@polygen.uucp (Pablo Halpern) (03/22/88)
From article <25699@cca.CCA.COM>, by g-rh@cca.CCA.COM (Richard Harter): > As it stands, the real value of a prototype is informative -- > the calling routine can contain a description of a calling sequence. > Its value for protecting against erroneous usage of routines is nominal; > lint is really much more useful. A prototype declaration does not protect > you against an incorrect prototype declaration because it does not 'look' > at the real master (the actual declaration in the code in the called routine) > whereas lint does. Not true. Although prototypes do duplicate some of the purpose of lint, they do more than that. One example is telling the compiler what representation to cast NULL into (see other postings under this subject header). Other uses of prototypes: tell the compiler it doesn't need to coerce a float into a double or an int into a long. (ANSI implies that if a function call is made in the presence of a prototype, the programmer is responsible for making sure the same prototype is present for that function's definition, thus assuring that caller and called agree on how to pass the parameters. Unlike the Ada spec, X3J11 doesn't attempt to address the environment that keeps these things in sync.) Also, the compiler can figure out when to cast a short to an int or long when passing an integral type. This can be important when calling library routines. For example, X3J11 defines the argument of malloc() to be of type size_t, which is an implementation defined integral type. K&R defined the argument of malloc() to be an int, causing a problem for users of compilers where the address range doesn't fit in an int (e.g. 8086 large memory model: address range = 1M, int range = 64K). Using strict K&R definitions, code that allocated large chunks of memory was not portable to these machines because the argument of malloc() wouldn't fit in an int. Redefining malloc() to take a long doesn't work either because every use of an integer literal as an argument to malloc() would have the wrong type (int instead of long) and would be passed incorrectly by the compiler. Lint would catch these errors but that doesn't address the problem that the same code won't work on two different machines. Prototypes solve the problem by allowing the compiler writer to define size_t as long without forcing the compiler user (programmer) to change his/her code. The compiler now knows (assuming a prototype for malloc() is included in the right places) to cast integer arguments into longs (size_t) before passing them to malloc(). So you see, prototypes can significantly improve the portability of C programs. "Prototypes: they're much more than error detectors." Pablo Halpern | mit-eddie \ Polygen Corp. | princeton \ !polygen!pablo (UUCP) 200 Fifth Ave. | bu-cs / Waltham, MA 02254 | stellar /