Rules
The following C coding rules relate to header files:
Rule 4.2.a.) There shall always be precisely one header file for each source file and they shall always have the same root name.
Rule 4.2.b.) Each header file shall contain a preprocessor guard against multiple inclusion, as shown in the example below.
Rule 4.2.c.) The header file shall identify only the procedures, constants, and data types (via prototypes or macros, #define
, and struct
/union
/enum
typedef
s, respectively) about which it is strictly necessary for other modules to know about.
- It is recommended that no variable be declared (via
extern
) in a header file. - No storage for any variable shall be defined in a header file.
Rule 4.2.d.) No header file shall contain a #include
statement.
Example
#ifndef _ADC_H #define _ADC_H ... #endif /* _ADC_H */
Reasoning
The C language standard gives all variables and functions global scope by default. The downside of this is unnecessary (and dangerous) coupling between modules. To reduce inter-module coupling, keep as many procedures, constants, data types, and variables as possible hidden within a module’s source file.
Exceptions
It is acceptable to deviate from the common root name rule for the core application module (e.g., if “foomain.c
” contains main()
, its header file may be “foo.h
”).
Under certain circumstances it may be necessary to share a global variable across modules. Whenever this is done, such a variable shall be named with the module’s prefix, declared volatile
, and always protected from race conditions at each location of access.
Enforcement
These header file rules shall be enforced during code reviews.
Comments:
Official Correction
Owners of the printed book or PDF should take note that the words "defined" and "declared" were inadvertently swapped in the original phrasing of rules 4.2.c.i and 4.2.c.ii. The phrasing on this web page version has been corrected to use those terms in a manner consistent with their formal meanings in the ISO C standards.
Rule 4.2.d
Official Reply Below
See our official reply on the rationale a bit further down in the comments list.
Reserved Identifiers
I break Rule 4.2.a every day (almost)
Substitutable files IMO is better than preprocessor conditional logic for dealing with different hardware variants.
Another case where this rule could/should be broken it exemplified by stdio.h. It does not have a single c file. If you are making a facade over a subsystem, it is acceptable to have multiple implementation files.
We Like Your Reasoning
You make a good point. For now, note that you're doing this via the exception process. In the future we will consider rewording the rule to allow for this specifically.
Use abstract data types when you can
4.2.c - Let me add a preference to keeping struct details out of header files.
Prefer struct forward declaration in header files and full declaration of struct members in the C file, when possible.
For example:
CircularBuffer is an abstract data type. No user of the data structure can know the internals as they are not published in the header file. And thus no user can become dependent on the inner details of how the CircularBuffer is managed.
That's generally good advice.
That's generally good advice.
There's a blog post on that and related points here:
Header files should care for their own dependencides
A more accepted practice is that a header file takes care of its own dependencies, making using an interface easier to use. A source file implementing some interface defined by a header file should be able to be included first in the C source file. This proves the header file (and possibly the build script) take care of all the dependencies.
Imagine the usual maintenance of a code base results in adding a new API to a module. This API requires a typedef from a different header file. If you follow the rule as written, all the clients would have to be modified to add the needed dependency. When header files take care of their own interface, clients only need to be recompiled.
All that said, prefer forward declarations in header files over includes when possible (like the CircularBuffer example).
4.2.d prevents including types, etc
Conventional. But not wise?
Though we agree it is "conventional" to disobey this rule (as addressed in answer to another comment below), we do not consider that choice "wisdom".
Rule 4.2.d
What is the reasoning for no #includes in a header file? What about the use of non standard variable types?
Official Reply
Though it is contrary to widespread programming practice, we have found that our rule limiting use of #include statements to .c files is a net bug reducer. First and foremost, it forces you to think about the interrelationships between software modules in your architecture. If you are having trouble following this rule, chances are your architecture is more like spaghetti code than an actual decomposition of the problem.
With respect to non-standard data types (e.g., typedef struct { ... } my_type;) this rule forces you to #include the header that defines your custom type earlier in any .c file than other headers that rely on that type.