Barr Group FacebookBarr Group TwitterBarr Group LinkedInBarr Group Vimeo

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 typedefs, respectively) about which it is strictly necessary for other modules to know about.

  1. It is recommended that no variable be declared (via extern) in a header file.
  2. 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:

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.

I really don't understand rule 4.2.d. If a header file does not #include definitions for the types it uses, it is required that the .c file include them. This makes it much harder to determine the source of a type and to reuse the header file in another project or even file.

See our official reply on the rationale a bit further down in the comments list.

Identifiers beginning with an underscore and an uppercase letter are always reserved for any use.

Rule 4.2.a One header one C restriction excludes creating link-time substitutable files. This is an essential technique for automated testing and a good idea for handling variation in designs. For example having two different driver implementations with the same interface and the correct implementation is chosen at link-time to match the hardware. There would also be a third driver implementation to use as a test stub (fake, sky, mock) during unit testing higher level portable code.

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.

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.

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:

#ifndef CIRCULAR_BUFFER_INCLUDED
#define CIRCULAR_BUFFER_INCLUDED

#include 

typedef struct CircularBufferStruct CircularBuffer;

CircularBuffer * CircularBuffer_Create(unsigned int capacity);
void CircularBuffer_Destroy(CircularBuffer *);
bool CircularBuffer_IsEmpty(CircularBuffer *);
bool CircularBuffer_IsFull(CircularBuffer *);
bool CircularBuffer_Put(CircularBuffer *, int);
int CircularBuffer_Get(CircularBuffer *);
unsigned int CircularBuffer_Capacity(CircularBuffer *);
void CircularBuffer_Print(CircularBuffer *);

#endif

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.  

There's a blog post on that and related points here: 

Rule 4.2.d suggests that header files cannot include other header files. I really think this is a bad idea. It puts the burden of tracking down header file dependencies on clients of the interface. Header files should be responsible for including any dependencies needed by the header file so a client of the interface can include it and call it and not need to hunt down a bunch of dependencies.

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 seems to go against most conventional wisdom that a module should include (reservedly) other modules needed for its use. E.g. if a public function returns a custom type, such as `int16_t` then the header will /need/ to #include the stdint header to compile or at least require that the users #includes *and* the implementations #includes in the related C file maintain a particular ordering.

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".

What is the reasoning for no #includes in a header file?  What about the use of non standard variable types?

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.

What’s happening and how it’s done. Get in the know.

Sign up for our newsletter today!

Receive free how-to articles, industry news, and the latest info on Barr Group webinars and training courses via email. 

To prevent automated spam submissions leave this field empty.