What are the benefits of a relative path such as "../include/header.h" for a header?

CIncludeHeader

C Problem Overview


I've reviewed questions How to use include directive correctly and C++ #include semantics and neither addresses this - nor do the others suggested by SO when I typed the title...

What, if any, are the benefits of writing:

#include "../include/someheader.h"
#include "../otherdir/another.h"

compared with using just a plain file name:

#include "someheader.h"
#include "another.h"

or perhaps a relative name without the '..':

#include "include/someheader.h"
#include "otherdir/another.h"

The problems I see are:

  • You can't move a header without worrying about which source files include it.
  • You can end up with very long paths for headers in dependencies and error reports. I had one today with "../dir1/include/../../include/../dir2/../include/header.h".

The only merit I can see is that while you do not need to move files around, you might be able to get away without always using '-I' directives to find headers, but the loss of flexibility, and the complexity of compiling in sub-sub-directories, etc seems to outweigh the benefit.

So, am I overlooking a benefit?


Thanks for the inputs. I think the consensus is that there aren't any major benefits to the notation using ".." that I'm overlooking. In general terms, I like the "somewhere/header.h" notation; I do use it in new projects. The one I'm working on is anything but new.

One of the problems is that there are various sets of headers, often with a prefix such as rspqr.h, rsabc.h, rsdef.h, rsxyz.h. These are all related to code in the rsmp directory, but some of the headers are in rsmp and others are in the central include directory, which does not have sub-directories such as rsmp in it. (And repeat for the various other areas of the code; there are headers in multiple locations, needed randomly by other bits of code.) Moving stuff around is a major problem because the code has gotten so convoluted over the years. And the makefiles are not consistent in which -I options are provided. All in all, it is a sad story of not-so-benign neglect over a period of decades. Fixing it all without breaking anything is going to be a long, tedious job.

C Solutions


Solution 1 - C

I prefer the path syntax as it makes it very clear what namespace or module the header file belongs to.

#include "Physics/Solver.h"

Is very self-describing without requiring every module to prefix their name to header files.

I almost never use the ".." syntax though, instead I have my project includes specify the correct base locations.

Solution 2 - C

The problem with #include "../include/header.h" is that it will often work by accident, and then a seemingly unrelated change will make it stop working later.

For example, consider the following source layout:

./include/header.h
./lib/library.c
./lib/feature/feature.c

And let's say that you're running the compiler with an include path of -I. -I./lib. What happens?

  • ./lib/library.c can do #include "../include/header.h", which makes sense.
  • ./lib/feature/feature.c can also do #include "../include/header.h", even though it doesn't make sense. This is because the compiler will try the #include directive relative to the location of the current file, and if that fails, it will try the #include directive relative to each -I entry in the #include path.

Furthermore, if you later remove -I./lib from the #include path, then you break ./lib/feature/feature.c.

I find something like the following to be preferable:

./projectname/include/header.h
./projectname/lib/library.c
./projectname/lib/feature/feature.c

I wouldn't add any include path entries other than -I., and then both library.c and feature.c would use #include "projectname/include/header.h". Assuming that "projectname" is likely to be unique, this should not result in name collisions or ambiguities in most circumstances. You can also use the include path and/or make's VPATH feature to split the project's physical layout across multiple directories if absolutely necessary (to accommodate platform-specific auto-generated code, for instance; this is something that really breaks down when you use #include "../../somefile.h").

Solution 3 - C

IANALL, but I don't think you should be putting ..'s in actual C or C++ source files, because that's not portable and the standard does not support it. This is similar to using \'s on Windows. Only do it if your compiler can't work with any other method.

Solution 4 - C

Begin the path of your #include "" directives with a sequence of one or more "../"s when:

  • you want to include a file whose collocation with the including file is fixed and
  • you are building on a POSIX system or with VC++ and
  • you wish to avoid ambiguity about which file will be included.

It is always easy to provide an example of where your code-base contains an error and where this subsequently causes a hard-to-diagnose failure. However, even if your project is fault-free, it can be abused by a third party if you rely on absolute paths to specify files that are located relative to one another.

For example, consider the following project layout:

./your_lib/include/foo/header1.h
./your_lib/include/bar/header2.h
./their_lib/include/bar/header2.h

How should your_lib/include/foo/header1.h include your_lib/include/bar/header2.h? Let's consider two options:

  1. #include <bar/header2.h>

    Assuming both your_lib/include and their_lib/include are cited as header search paths (e.g. using GCC's -I or -isystem options), then the choice of which header2.h will be chosen is sensitive to the order in which those two paths are searched.

  2. #include "../bar/header2.h"

    The first location in which the compiler will search is the location of your_lib/include/foo/header1.h, which is your_lib/include/foo/. It will first try your_lib/include/foo/../bar/header2.h which reduces to your_lib/include/bar/header2.h where it will find the correct file. The header search paths will not be used at all and there is little room for ambiguity.

I would strongly recommend option 2) in this case for reasons given.

In response to some arguments in other answers:

  • @andrew-grant says:

    > ...it makes it very clear what namespace or module the header file belongs to.

    Maybe. But relative paths can be interpreted as "in this same module". They provide clarity in the case that there are multiple directories with the same name located in different modules.

  • @bk1e says:

    > ...it will often work by accident...

    I'd argue that relative paths will only work by accident in very rare cases where the project was broken from the start and could easily be fixed. To experience such a name collision without causing a compiler error seems unlikely. A common scenario is where a dependent project's file includes one of your headers which includes another of your headers. Compiling your test suite should result in a "No such file or directory" error when compiled in isolation from that dependent project.

  • @singlenegationelimination says

    > ...that's not portable and the standard does not support it.

    The ISO C standard may not specify all the details of the systems under which a program is compiled or run. That does not mean that they are unsupported, merely that the standard doesn't over-specify the platforms on which C will run. (The distinction between how "" and <> are interpreted on common modern systems likely originates in the POSIX standard.)

  • @daniel-paull says

    > the ".." assumes relative location and is fragile

    Fragile how? Presumably sensitive to the collocation of the two files. Thus ".." should only (and always) be used when the author of the including file controls their location.

Solution 5 - C

Think of your source tree as a nested namespace and the include path is allowing you to pull directories into the root of this namespace. The question is then one of forming a logical namespace for your code base irrespective of how the code is organised on disk.

I would avoid paths like:

  • "include/foo/bar.h" — the "include" seems illogical and redundant
  • "../foo/bar.h" — the ".." assumes relative location and is fragile
  • "bar.h" — unless bar.h is in the current directory, this pollutes the global namespace and is asking for ambiguities.

Personally, I tend to add a path like the following to my projects include path — "..;../..;../../..;../../../..".

This allows you to apply a sort of hiding rule to your #includes and allows some freedom of moving headers without breaking other code. Of course this is at the expense of introducing a risk of binding to the wrong header file if you are not careful as non-fully qualified names may be (or become over time) ambiguous.

I tend to fully qualify #includes in public headers so any third parties consuming my code do not need to add the "..;../..;../../..;../../../.." to their project — it's just a convenience for my private code and build system.

Solution 6 - C

Another issue on windows with relative paths is MAX_PATH. This will trigger compile issues when e.g. cross compiling for android and your path grows bigger than 260 in length.

Solution 7 - C

Because then you place the file relative to the root of the project, and when you check it into source control and another developer checks it out to a different location on their local system things still work.

Solution 8 - C

Throwing out another argument for absolute (or "extended relative") include paths like #include "./lib/subdir/~/subsubsubsubdir/header.h"... When working on a really large project one may require lots of "-I./~" options on the link line. In a current project of mine the number of characters taken up by those options is nearly 40k which causes errors due to command line limitations.

Though usually I dislike the apparent inflexibility of that style of include path it can help avoid this sort of problem. For example a static library could provide its API through "#include "lib_api.h" and all other organization could be relative to that location and invisible to the user.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionJonathan LefflerView Question on Stackoverflow
Solution 1 - CAndrew GrantView Answer on Stackoverflow
Solution 2 - Cbk1eView Answer on Stackoverflow
Solution 3 - CSingleNegationEliminationView Answer on Stackoverflow
Solution 4 - CJohn McFarlaneView Answer on Stackoverflow
Solution 5 - CDaniel PaullView Answer on Stackoverflow
Solution 6 - CMadSystemView Answer on Stackoverflow
Solution 7 - CJoel CoehoornView Answer on Stackoverflow
Solution 8 - CoclykeView Answer on Stackoverflow