Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

c

include

header

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.

like image 238
Jonathan Leffler Avatar asked Feb 28 '09 01:02

Jonathan Leffler


People also ask

What is the use of relative path?

A relative path refers to a location that is relative to a current directory. Relative paths make use of two special symbols, a dot (.) and a double-dot (..), which translate into the current directory and the parent directory. Double dots are used for moving up in the hierarchy.

What is include path in C?

For include files that are specified as #include "path-spec" , directory search begins in the directory of the parent file and then proceeds through the directories of any grandparent files. That is, the search begins relative to the directory that contains the source file that's being processed.

When would you use a relative path vs an absolute path?

In simple words, an absolute path refers to the same location in a file system relative to the root directory, whereas a relative path points to a specific location in a file system relative to the current directory you are working on.

How do I add a header file in Makefile?

The only way to include the header file is to treat the filename in the same way you treat a string. Makefiles are a UNIX thing, not a programming language thing Makefiles contain UNIX commands and will run them in a specified sequence.


5 Answers

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.

like image 118
Andrew Grant Avatar answered Oct 21 '22 02:10

Andrew Grant


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").

like image 38
bk1e Avatar answered Oct 21 '22 00:10

bk1e


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.

like image 41
SingleNegationElimination Avatar answered Oct 21 '22 02:10

SingleNegationElimination


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.

like image 10
John McFarlane Avatar answered Oct 21 '22 00:10

John McFarlane


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.

like image 2
Daniel Paull Avatar answered Oct 21 '22 02:10

Daniel Paull