I am preparing for a discussion with my fellow programmers which will be about their use of the C/C++
#include directive. The codebase which I have to retrofit to Automotive standards is using includes of the form
#include <path/out/of/the/blue.h>. To be precise: the projects carry around a large set of include paths for the compiler (
-Iinclude/me etc.) BUT the path expressions even reach outside of these places so that
blue.h can only be found if the compiler internally produces a combination of all include paths with the path in the statement itself:
path/out/of/the/blue.h. There are many gripes I have against this practice:
<>is reserved for system headers coming from the platform, and are strongly discouraged to be used by project code. The compilation only works because the C and C++ standard requires the compiler to search again as if the file was given with
""if it is not found on the first pass.
- It creates a review nightmare: the include file is neither at the path rooted at the directory where the including C or C++ file sits nor is it in any of the include locations, you have to repeat the search of the compiler to eventually find it somewhere, yet at this point in time you aren't really trusting yourself - the compiler could have searched differently.
- There are a number of multiple file names in the project tree:
blue.hcan be found at a number of locations and sometimes one
blue.hserves as a dispatcher file for the inclusion of a more specific, the true
blue.hdown the directory tree. Which
blue.his selected is distinguished by
#define PLATFORMmacros and the like.
- It creates a monolithic, reorganization-resisting project structure, coupling C and C++ interfaces (which live in directory-less space as far as the language is concerned) with the file system.
- It spills over into new projects by backfiring into the build system: As soon as one uses a header which itself includes other path-dependent headers, the new projects build script has to adapt to this usage.
We are using mbed-os and it looks like its source tree suffers from the same in (IMHO) bad code structuring choice.
As a TL;DR one could say that I have the firm belief that one is ill-advised to carry the project structure into the source code. One has to supply a lot of structure and dependencies to the build system and linker anyway - introducing a secondary coupling per the source files wreaks havoc at least when one tries to change the build system (as I am forced to now).
What is the public opinion on this? How flat or tree-like do you manage your includes?
PS: MISRA is only remotely talking about this issue though one could read it as "don't use anything else but header file names"
PPS: I am not completely against the use of paths (well, I am in my code, but I could live with this in inherited code) as long as this isn't visible from the outside but the current versions of the projects rather forces one to adapt to exactly this usage.
PPPS: to give you an idea where carelessness regarding physical structure leads to, here a part of the include paths which are required to compile mbed-os which I mentioned before:
mbed-os/features/nanostack/mbed-mesh-api/ mbed-os/features/nanostack/sal-stack-nanostack-eventloop/ mbed-os/features/nanostack/mbed-mesh-api/mbed-mesh-api/ mbed-os/features/nanostack/mbed-mesh-api/source/include/ mbed-os/features/nanostack/nanostack-interface/ mbed-os/features/nanostack/sal-stack-nanostack-eventloop/nanostack-event-loop/ mbed-os/features/lwipstack/ mbed-os/features/lwipstack/lwip-sys/ mbed-os/features/lwipstack/lwip/src/include/ mbed-os/features/lwipstack/lwip/src/include/lwip/
Some of these paths are just starting points for a deeper reach (i.e. "sub/subsub/inc_this.hpp") and some are plain old "there you will find your includes" directories.
This leads to yet another counter argument to anything beyond the simple "set your include paths to where your include files are" rule: it is obviously impossible to communicate anything more complex over time and different coding cultures.