4

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: include/me/+path/out/of/the/blue.h. There are many gripes I have against this practice:

  • AFAIK <> 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.h can be found at a number of locations and sometimes one blue.h serves as a dispatcher file for the inclusion of a more specific, the true blue.h down the directory tree. Which blue.h is selected is distinguished by #define PLATFORM macros 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.

10
  • "AFAIK <> is reserved for system headers coming from the platform, and are strongly discouraged to be used by project code." That assumption is wrong. Mar 27, 2020 at 18:11
  • @πάντα ῥεῖ So which is the correct take on this then? BTW your user handle is particularly nasty to reply to from a mobile device #justsaying Mar 27, 2020 at 18:52
  • 3
    There's no particular correct way. Best let your build system handle that. But saying that the <> syntax is reserved for platform headers is plain wrong. Regarding my nick, that's intentional of course :-P. Mar 27, 2020 at 18:59
  • 3
    A style guide suggestion is not the same as "reserved for system headers." Mar 27, 2020 at 19:02
  • 3
    Check out the Gnu documentation: "#include <file> -- This variant is used for system header files. It searches for a file named file in a standard list of system directories. You can prepend directories to this list with the -I option." Mar 27, 2020 at 19:04

1 Answer 1

8

I think the key issue here is a clear and consistent project structure and good names. An unstructured monolith with twelve different blue.h files in different directories at different depths in the directory tree is going to feel like the mess it is, no matter what the rules for #include are.

Your project

As general as your examples are it’s hard to give a recommendation for your specific case. For a large legacy project a major restructuring might very well be too expensive and you’ll have to ask yourself the usual question: With the given amount of resources which course of action is expected to reduce the pain the most?

A solution that might be at least simple-ish to achieve could be shifting things from the -I flags into the #include directives; for instance from:

-Iinclude/me
#include <path/out/of/the/blue.h>

to

-Iinclude
#include <me/path/out/of/the/blue.h>

On the one hand that makes the paths even longer. On the other hand it makes them more specific. After all, you see the header paths in the code, but not the -I flags.

My #include conventions

May they serve as inspiration. :)

  • #include "" is reserved for private use, such as a cpp file including a local, private header file. It is supposed to be filename only, but referring to a header from a subdirectory is not out of the question.
  • #include <> is for external dependencies from outside the current software component or for a component’s own public headers. On the technical level a software component is usually a library. External can be a 3rd party dependency or a different component of a multi-component project.

Header paths carry semantic information and are supposed to be structured like this:

#include <project/component/possibly/subdirs/header.hpp>

Let’s say you have a cat project with components paw and tail. Then tail might include:

#include <boost/filesystem/directory.hpp>
#include <cat/paw/claw.hpp>

In both cases the path is an essential part of the include directive. It tells you exactly which project and component the header belongs to. I do partly agree with you, though. The possibly/subdirs part of the path should be as short as possible, ideally not used at all. An increasing number of subdirectories is an indicator that a component is growing too large.

However, the project/component part is vital. Without it you have

#include <directory.hpp>
#include <claw.hpp>

and lose so much information that the #include lines become almost useless for finding the headers – at least without “jump to header” IDE goodness.

To avoid confusion between the directory hierarchy and the namespace hierarchy I keep them in sync. The code in cat/paw/claw.hpp should be implemented in the cat::paw namespace unless a compelling reason exists against it.

What I ban outright is accessing the parent directory in header paths for both the <> and "" notations. If you need:

#include "../../path/to/header.h"

you have a bug in your project structure. Go and fix it.

12
  • I tend to agree, but I struggle to avoid the issue described in your last paragraph: my main project does not violate this, and never needs ../, but there is a Unittest project, which is in a separate and parallel path (the sources shouldn't mix with the productive code), and to use the same includes, it is bound to use ../ (or to add the productive project's path in its -I directive). How would you avoid the issue here?
    – Aganju
    Mar 31, 2020 at 22:27
  • I disagree on the claimed uselessness of include files given only by name. As I said C/C++ does not lend itself to a hierarchical organization of compilation units - if there is a hierarchy, it comes from the control flow, namespaces and structured data types. It is IMHO the wrong approach to add another organization parallel to these, moreover one which is completely transient and optional. Organize your sources the way you want but do that in a non-intrusive way as far as compilation is concerned - and coincidentally this is also what the C/C++ standard aims for. Apr 1, 2020 at 9:20
  • I am talking from an Automotive Software point of view, or to be more precise, from the configuration management part of it, and there is simply not much room for imprecisions in there. Are you manageing your project under the same authoritative restrictions (ASPICE, MISRA, etc.)? I would not call such strict guidelines into effect on our code if I had not to. Apr 1, 2020 at 9:38
  • 2
    @Vroomfondel I don’t understand how you can consider the #include <directory.hpp> useful. Where that header is from is full blown guess work. But I do understand your concern about the dual hierarchies. For my personal stuff I use the same hierarchy for namespaces and directories. At work we have to deal with ASPICE, not MISRA though. There the hierarchies are inconsistent and it is sometimes messy. Doesn’t seem to be a problem for ASPICE compliance, though.
    – besc
    Apr 1, 2020 at 15:40
  • 2
    Paths in includes are +1 super good. In my opinion (a) every external project should export all of its PUBLIC headers under one directory, which is the name of that project, and (b) every compile line should include -I<path-to-root> where <path-to-root> is the parent of the project include dir. So if you're including header header.h from project foo under /my/projects it should be -I/my/projects and #include <foo/header.h>. There's simply no way to guarantee that two external projects don't use overlapping header file names!! Nov 22, 2021 at 21:39

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.