© Mikael Olsson 2019
Mikael OlssonModern C Quick Syntax Referencehttps://doi.org/10.1007/978-1-4842-4288-9_21

21. Headers

Mikael Olsson1 
(1)
Hammarland, Länsi-Suomi, Finland
 

As a project grows it is common to split the code up into different source files. When this happens, the interface and implementation are generally separated. The interface is placed in a header file, which commonly has the same name as the source file and a .h file extension. This header file contains forward declarations for the source file entities that need to be accessible to other compilation units in the project.

Why Use Headers

C requires everything to be declared before it can be used. It is not enough to just compile the necessary source files in the same project. For example, if a function is placed in func.c, and a second file named app.c in the same project tries to call it, the compiler will report that it cannot find the function (or, prior to C99, that it has implicitly declared it).
/* func.c */
void myFunc(void) {
  /* ... */
}
/* app.c */
int main(void) {
  myFunc(); /* error: myFunc identifier not found */
}
To make this work as intended, the function's prototype has to be included in app.c.
/* app.c */
void myFunc(void); /* prototype */
int main(void) {
  myFunc(); /* ok */
}
This can be made more convenient if the prototype is placed in a header file named func.h and this header is included in app.c through the use of the #include directive. This way, when changes are made to func.c, there is no need to update the prototypes in app.c. Furthermore, any source file that wants to use the shared code in func.c can just include this one header.
/* func.h */
void myFunc(void); /* prototype */
/* app.c */
#include "func.h"

What to Include in Headers

As far as the compiler is concerned there is no difference between a header file and a source file. The distinction is only conceptual. The key idea is that the header should contain the interface of the implementation file—that is, the code that other source files will need to use. This may include shared macros, constants, and type definitions, as those shown here.
/* app.h - Interface */
#define DEBUG 0
const double PI = 3.14;
typedef unsigned long ulong;
The header can also contain prototypes of the shared functions defined in the source file. Internal functions used only within the source file should be left out of the header, to keep them private from the rest of the program.
void myFunc(void); /* prototype */
Additionally, shared global variables are typically declared as extern in the header, while their definitions lay in the source file.
/* app.h */
extern int myGlobal;
/* app.c */
int myGlobal = 0;

It should be noted that the use of shared global variables is discouraged. This is because the larger a program becomes, the more difficult it is to keep track of which functions access and modify these variables. The preferred method is to instead pass variables to functions only as needed, in order to minimize the scope of those variables.

The header should not include any executable statements, with one exception. A shared function that is declared as inline needs to be defined in the header. Otherwise, the compiler will not have the definition necessary for inlining the function available to it.
/* app.h */
inline void inlineFunc(void) {}
If a header requires other headers, it is common to include those files as well, to make the header stand alone. This ensures that everything needed is included in the correct order, solving potential dependency problems for every source file that needs the header.
/* app.h */
#include <stddef.h>
void mySize(size_t);

Note that since headers mainly contain declarations, any extra headers included should not affect the size of the program, although they may slow down the compilation.

Include Guards

An important thing to bear in mind when using header files is that a code entity, such as a constant, typedef, or enum, may only be defined once in every project. Consequently, including the same header file more than once will often result in compilation errors. The standard way to prevent this is to use a so-called include guard. An include guard is created by enclosing the body of the header in a #ifndef section that checks for a macro specific to that header file. Only when the macro is not defined is the file included. The macro is then defined, which effectively prevents the file from being included again.
/* app.h */
#ifndef APP_H
#define APP_H
/* ... */
#endif