How we change what others think, feel, believe and do
CHAPTER 7 : File Layout
7.4 Header files
Header files simplify code and data files by removing a possibly large number of declarations, etc. from the start of the file. In doing this, however, they make the removed items effectively invisible to the reader of the file. The programmer should counteract this by organizing the contents and layout of the header files so that the reader can easily find the missing items.
A header file should 'serve' a coherent group of code files. A small program might have just one header file. A larger program would have more header files, divided to reflect the division of these code files.
There are several possible uses for header files: a system interface file provides the external world with all it needs to access this system; a system common file contains common things used within this system; a system specific file contains items used only within a specific portion of this system.
System interface files may contain function prototypes, data interface to the system, including data structure declarations, error codes, etc. They act as the 'ambassador' for the subsystem, and this may be the only files that you write that other users ever read. Thus special care should be taken to ensure that these files are particularly complete, well structured and clearly commented.
There can be several levels of interface header files, depending on their effective scope for example to an external platform (eg. operating system, windowing system) or an internal subsystem (eg. keyboard interface).
7.4.2 Memory reservation
Header files can contain any C item, but commonly do not contain any data definitions (ie. those that reserve memory), as this could result in multiple instances of the same symbol being declared in different files, which would cause linker resolution problems. It also may be unclear who 'owns' the item. Similarly, executable code in header files is to be avoided (other than in macros). By insisting that header files are completely passive, causing no memory to be reserved for data or for code, readers and users can be confident that no definitions are 'invisible' to them.
7.4.3 Header file interdependency
It can make for confusing reading if header files interact or have some dependency on one another. Consider the case where one header file must be #include'd before another:
/* sysio.h */
This makes it difficult for the reader of 'kbdio.h' to find where MAX_BUF_SIZE is declared. It is also an additional awkward piece of information to remember for anyone extending the system. Confusion caused by this can be avoided by nesting header files:
/* kbdio.h */
This, however, can cause problems of duplicate symbols (which are not allowed) if 'sysio.h' is included twice. This can be avoided by bracketing the contents of header files with an '#ifndef':
/* sysio.h */
It is not always possible to eliminate interaction between header files without causing worse problems. Nevertheless, it will help to improve readability if interaction is minimized.
7.4.4 Header files and declarations
Where a data item is used by multiple functions, the best place for its description is in a single header file. This is then #include'd in the code file that defines and the file that uses that data. It also allows constants associated with the data to be defined within the same context.
/* kbddata.h */
7.4.5 Layout of header files
As with all files, the header file should start with a header comment, explaining its scope and contents, followed by a number of sections preceded by subheader comments. The layout of rest of the file will depend very much on its contents.
The layout could simply be by statement type, putting declarations earlier if they more likely be used elsewhere, thus: #include's, then #define's, enum's, struct's, typedef's and function declarations.
A better approach is to divide it by functional area so that all items pertaining to a single usage or group of usages are placed together (thus minimizing visual scope). A good test of this is that if the file becomes too big, the points at which it may be split are naturally available. Care must be taken when doing this that dependent items are in the correct order. This can be helped by sorting within these groupings by type, as above. Thus, a typical format might be:
File heading comment
Common context items:
Functional area 1:
#define's, enum's for context
Data description 1:
enum's for context
typedef or struct to declare data item
#define's for constant values
Data description 2:
External function declarations
Functional area 2:
#endif /* filename_h */
Note that having several functional areas in one header file is a part of the complexity balancing act between scope and file size. It may quite reasonable to have a header file containing only one functional area or even one data description, such as a message identification header file, which contains only #define's for external message numbers.
And the big