Header files in C/C++ are defining the interface between different modules. In this article I share some tips and tricks how create such interface files.
Creating Header File
Most IDEs have a helper to create header files, including Eclipse.
What I recommend is to add a custom text with license information. How to dos this is described in Custom C/C++ Headers with Eclipse.
Guards against recursive includes
Every header file shall have a #ifndef … #define … #endif as shown in the example, to avoid recursive includes. Recursion can happen if a header file gets either directly or indirectly included again.
Because there is only one global namespace for a #define, the symbol (MAIN_STEPPER_H_) shall be unique in the whole application. Eclipse helps with this using a define with the format <folder name>_<file name>_H_ to reduce chances of conflicts with other header files. This is just a convention, and can be changed if needed.
If the implementation is implemented in C, but if I want to use it from C++ too, then I should add the conditional extern “C” as below:
With the ‘extern “C'” the C++ compiler knows that the implementation is done in C, because the name encoding (functions, parameters) are different between C and C++. Adding C++ support is of course optional, but I recommend it so you can use the module with C++ later if needed.
Below is an example of a function added to the interface:
I prefer to use a prefix (in this case STEPPER_): that way I see always from the code where the implementation is located.
Note that a function interface could be written with the ‘extern’ in front of it:
extern void STEPPER_Init(void);
but usually I’m too lazy to have it added, although some might prefer to have it added for consistency and clarity.
Module Initialization and De-Initialization
For me it is a good practice to provide a initialization and de-initialization interface to each module, even if they are empty. Think about a module constructor and destructor. The role is to initialize the internal state or variables (if any).
In my view, variables should not be exposed in interfaces, whenever possible. But if you decide to have it in the interface, do not forget to mark it extern:
Unlike for functions, the extern is mandatory for variable declarations:
In the supplementation (.c file) it would habe be defined like this:
/* implementation in stepper.c */ int STEPPER_Flags = STEPPER_DEFAULT_FLAGS;
As another rule: never define objects in the header file. It means never use ‘static’ for anything in the interface! ‘Static’ marks things with internal linkage, and internal things have no meaning in an (external) interface.
💡 Of course, there *can* be reasons for using static, but these are rare edge cases, e.g. if a header file is well-controlled (e.g. generated some kind of code or tables) and included only once.
One special case and imho the only exception is with using inline:
As for variables and functions (which includes inline definitions), it is about linkage: if that object can be linked outside or if it is invisible to other translation units. Having the inline function marked as static, only makes it visible inside the current unit, which makes sense.
One thing to keep in mind is: inline is just a hint (like register), and there is no guarantee that the compiler will actually inline that code. If it does not get inlined, it will be handled like if this would be a local static function. Or in other words: in such a case, if you include that header file multiple times and use that inline function in that compilation unit, you will end up with multiple (static) instances of that (not) inlined function! The same applies to ‘static const’ too!
In essence, you need to keep in mind that including a header file means for the pre-processor to ‘copy-paste’ the content for the current compilation unit. If you have static items in the header file, you end up with multiple static definitions, resulting in copies of that object for each compilation unit.
Now what about just ‘inline’ or even ‘extern inline’? Have a read at this StackOverflow article.
Instead exposing data in an interface, I recommend to write setter and getter functions instead.
This ensures encapsulation of the data and makes future modifications much easier.
A header or interface file shall be self-contained: it means that a module using it can simply include the interface and automatically includes the necessary other interfaces. Or in other words: whatever dependencies exist in the interface, it shall be resolved in the interface itself.
In the example below the interface uses the ‘bool’ type which is not part of the ‘standard C’. Therefore I have to include that header file for it (stdbool.h) to make sure the type is known:
Of course an interface or header file needs some kind of documentation or description. What I prefer to use is a doxygen style of documentation.
Doxygen is a great tool which follows a ‘single-source’ approach: the source files are the documentation, and the documentation (HTML, PDF, …) can be derived from the source files: this removes the need to keep a separate documentation in place (at least in many cases).
Writing good header files and interfaces is important. I hope I gave you some food for thoughts and a few ideas how to create and maintain interface files.