[comp.lang.c] Prototypes, was Why NULL is 0

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   /