Prototypes style question

James C Burley burley at world.std.com
Sat Nov 3 03:00:51 AEST 1990


In article <1990Oct30.182347.28809 at arcturus.uucp> evil at arcturus.uucp (Wade Guthrie) writes:

   I would like to ask what, if any, style standard exists for the 
   distribution of prototypes within header files for a large project.
   Should my project have a single "proto.h" which comprises all of the
   global (i.e., non-static) function prototypes or should separate
   headers exist?

   Thanks.
   -- 
   Wade Guthrie (evil at arcturus.UUCP)    | "He gasped in terror at what sounded
   Rockwell International; Anaheim, CA  | like a man trying to gargle while
   My opinions, not my employer's.      | fighting off a pack of wolves"
					|                Hitchhiker's Guide

I think most everyone has their own style, and most groups, and perhaps even
some companies.

But I'd recommend against using a single project-wide prototype file.

Think of organizing your source files around the objects they deal with: for
example, put all the code that "knows" about lexing into a lex file, all the
code that "knows" about expression parsing into an expr.c file, and so on.

Then, each source (.c) file contains functions and other things (global and
internal static data, internal static functions) that handle the internals of
the object (or sometimes objects, if they're intricately interwoven) and
present a uniform, abstracted interface to the outside world.  By uniform, I
mean you present functions that make sense to someone who knows what the
source file does, but not how it does it -- in particular, I'd suggest that
outside users never directly access global data defined within a source file.
Instead, make that access path via a macro that could double as a function so
changing how the module is implemented won't require changes to code that
uses it.  (This last statement is the ideal; not possible to realize fully
since often when designing it is not always clear just what the ideal
interface is and sometimes the ideal interface would just be too slow.  But
in each individual case, this ideal can often be reached 100%, and for the
cases that can't, use some consistent style like a strange naming convention
that clearly identifies in a mnemonic fashion, along with the more verbose
comments, that "special insight into a module's internal operation" happens
at various places outside the module.)

Now, for each such source file, write a .h file that completely describes the
interface to that source file.  Given the name of the file, xyz, enclose all
its actual "code" (declarations) with:

#ifndef XYZ_H
#define XYZ_H
...declarations...
#endif

These declarations provide the function prototypes you ask about along with
enums (or #defines) for all the constants defined by the module for outside
use.  They also provide "extern" declarations for global data accessed by
outside users via macros #define'd within the declarations.  I use a naming
convention of adding a trailing underscore to anything I define in a module's
.c or .h file that outside users aren't supposed to touch via their source
code but which they will access anyway via macros defined in the same .h
file, as in:

xyz.h:

...
extern int xyz_count_;
...
#define xyz_count() xyz_count_  /* Return current count. */
...

xyz.c:

...
int xyz_count_ = 0;  /* The actual storage for count. */
...

This way, a simple rule can be applied: a given module should not reference
any name ending in underscore (or substitute your own favorite naming
convention) unless the name it references is defined by itself (and not some
other module).  In any project larger than "quicky" I always stick the module
name in front of every name the module exports (global) anyway, and even go
as far as doing that for internal static names in the .c file (with "_"
suffixing the name).  Lots of people don't like this approach, but it works
for me and I never get naming conflicts.  Names get long but I type fast.
Object orientation (C++) helps reduce the need to do this sort of thing, I
suspect.

Now any .c file whose SOURCE code (not the cpp-processed level, i.e. once
#defines are resolved; that's "someone else's problem") references anything
declared by an outside module is supposed to "#include" the .h file for that
module.  Further, any .h file whose source code should do the same thing.
If you stick to certain orderings in the .h file, like doing the "easy"
things like enums, non-macro #defines (though macro #defines can be done here),
and "typedef" statements before the #includes, then doing the "struct"
definitions referenced by "typedefs", the function prototypes, the "extern"
definitions, and so on, you can even end up with a project where modules
interreference each other (as in "foo.h" defines structures incorporating
types defined by "bar.h" and vice versa) without having any ordering
dependencies for the #includes in any file.

Generally, I have every .c file include a project-wide file named project.h
which defines some truly common things I use like bool, TRUE, FALSE,
ARRAY_SIZE, CAT, etc, and each .c file also includes whatever standard headers
it needs.  Similarly, a .h file that itself depends on a standard header
includes it (though I don't think I've ever had such a dependency, but an
example might be, in xyz.h, "#define xyz_is_alpha(c) isalpha(c)", which means
xyz.h should #include <ctype.h>).  But .h files should not include project.h,
I think of that as a special case project-wide file.

I have a boilerplate for my .c and .h files that try to allow one to define
things in the appropriate orders.  Right now I know of a bug in my .c
boilerplate: it places the definitions of internal static storage (as in an
array used only within the .c code) before the function prototypes section.
This works unless you want an internal array containing pointers to internal
static functions, as I just discovered two days ago after using this basic
boilerplace for over a year!  So I'm going to move my function prototypes
above my internal static storage section, since the only things function
prototypes can depend on are types (and structs and such), but not internal
storage definitions, whereas internal storage definitions can depend on
function prototypes being present.

All this might seem like trouble, but once you have boilerplates that are
carefully thought out (or, like mine, had a chance to grow quickly in a
complicated application and are relatively stable), creating a new module
and connecting it up is a matter of filling in the blanks.  Further, I just
basically never have to waste time tracking down #include dependencies (which
on previous projects involving multiple programmers I've spent hours on once
in a while) or other egregious coding errors.  Naming conventions can be a
pain but if you think of a name as not only being a mnemonic for a description
but also for the location (the .h/.c file pair) and accessibility (public
or private) of a function, it'll seem less painful, and you'll begin to
reap the rewards (fewer silly bugs and quicker location of function decls and
defs) after sticking with these techniques for a while.  They aren't new or
anything -- data abstraction, data hiding (as much as is reasonable in C),
encapsulation, and so on -- but sometimes people think they can't be done in
C or done with optimal results, and I think they can and believe I've proved
it at least for myself.  (I've written a 50,000 line program over the last
year -- a Fortran Front End for a forthcoming GNU Fortran -- using these
techniques throughout, and they've been great.)

Do use a compiler with "require prototypes" or some such thing turned on.
Or use lint (which I don't have and have never yet used).  Don't waste another
moment of time run-time-debugging a parameter passing problem that could have
been detected earlier via prototypes!

Hope this rambling helps.  Let me know if you want me to email my .c and .h
boilerplates.

James Craig Burley, Software Craftsperson    burley at world.std.com



More information about the Comp.lang.c mailing list