The use of double include guards in C++
Asked Answered
K

5

73

So I recently had a discussion where I work, in which I was questioning the use of a double include guard over a single guard. What I mean by double guard is as follows:

Header file, "header_a.hpp":

#ifndef __HEADER_A_HPP__
#define __HEADER_A_HPP__
...
...
#endif

When including the header file anywhere, either in a header or source file:

#ifndef __HEADER_A_HPP__
#include "header_a.hpp"
#endif

Now I understand that the use of the guard in header files is to prevent multiple inclusion of an already defined header file, it's common and well documented. If the macro is already defined, the entire header file is seen as 'blank' by the compiler and the double inclusion is prevented. Simple enough.

The issue I don't understand is using #ifndef __HEADER_A_HPP__ and #endif around the #include "header_a.hpp". I'm told by the coworker that this adds a second layer of protection to inclusions but I fail to see how that second layer is even useful if the first layer absolutely does the job (or does it?).

The only benefit I can come up with is that it outright stops the linker from bothering to find the file. Is this meant to improve compilation time (which was not mentioned as a benefit), or is there something else at work here that I am not seeing?

Kibbutznik answered 19/6, 2017 at 11:17 Comment(21)
This just adds another layer of brittleness to the code. A second layer is completely unnecessary.Sterol
Not the linker, but the pre-processor. Honestly, any such benefit seems negligible to me on a modern build system, if you only include what you need. His "explanation" is more reminiscent of an expert beginner to be honest.Rusel
Did your coworker provide any example of the situation when this "second layer" would be useful? I mean it could be another "it's just how things are done here" situation.Showmanship
'it outright stops the linker from bothering to find the file' I wouldn't even say that improves any significant build performence. Without the second layer, it should result in the same behaviour.Carlos
@StoryTeller This was my concern, the argument for it was presented in such a way that we had to simply trust anecdotal evidence instead of some empirical evidence.Kibbutznik
If you want a more through exploration of the idea, you can read the relevant chapters in John Lakos' book. I recall he discusses include guards (but I could be wrong). Still, like I said, I don't think it would have too much of an impact.Rusel
Aren't include guards out-dated anyway as #pragma once seems to be supported by major compilers?Lalalalage
I think the "second layer" can be dangerous as it relies on the header-internal macro HEADER_A_HPP. Although their name rarely changes, what if someone changes them for some reason? A simpler anternative is #pragma onceHardi
@zett42: #pragma once is not standard, however it is supported by many known compilers.Canonist
@Lalalalage #pragma once is supported but is not standard.Crosby
One upon a time, there might have been one or two compilers stupid enough to open the file each time to check the include guard. No compiler produced in this millennium would do that, as it can just keep a table of files and include guards and consult that before opening the file.Oak
It's completely unnecessary. There is no benefit at all.Fanti
@Bo Persson I heard that MSVC didn't have that optimization until recently.Minnich
Note that names that contain two consecutive underscores (__HEADER_A_HPP__) and names that begin with an underscore followed by a capital letter are reserved for use by the implementation. Don't use them in your code.Wretch
#pragma once is supported by every major compiler. It is not a de jure, but a de facto standard.Obsolesce
MSVC didn't implement the optimization based on file names until recently, but it did have this optimization with #pragma once, and that's what everyone was encouraged to use. @MinnichWoven
Yes, it's for performance. In the old times, computers had much less resources, but much more users; saving a pointless file-processing did matter.Bandylegged
Besides, IF you're going to use a "second layer of protection", why not #define __HEADER_A_HPP__ in the source file also, below the #include. Only then would it have made sense. Not much though.Sioux
As @BoPersson indicated, modern (good) compilers can avoid preprocessing the entire file in cases like this, by recognising whether a header contains the include-guard pattern, memorising it (in any sufficiently unique way, whether by file or macro name), and outright skipping the header if encountered again. If a compiler lacks this, complain until they fix it! Don't waste time writing 'clever' tricks to do it yourself.Fabricant
@Pete Becker - you're correct - but the symbols in question here are eaten by the pre-processor and never come close to the compiler.Corrales
@Corrales -- the preprocessor is part of the compiler. Again: those symbols are reserved for use by the implementation. The reason for that is to provide a set of names that the compiler implementor and standard-library implementor can use without stepping on or being stepped on by user-written code. There's no need for user code to use names like that, and if you do, you risk mysterious breakage.Wretch
M
107

I am pretty sure that it is a bad practice to add another include guard like:

#ifndef __HEADER_A_HPP__
#include "header_a.hpp"
#endif

Here are some reasons why:

  1. To avoid double inclusion it is enough to add a usual include guard inside the header file itself. It does the job well. Another include guard in the place of inclusion just messes the code and reduces readability.

  2. It adds unnecessary dependencies. If you change include guard inside the header file you have to change it in all places where the header is included.

  3. It is definitely not the most expensive operation comparing the whole compilation/linkage process so it can hardly reduce the total build time.

  4. Any compiler worth anything already optimizes file-wide include-guards.

Meridel answered 19/6, 2017 at 11:24 Comment(2)
If you change include guard inside the header file you have to change it in all places where the header is included. .... well, technically, no you don't, but I think that kind of proves the point even further.Skipp
I have seen people do this when trying to get around some issues with Oracle Pro-C pre-processor. Still don't like it.Reactive
W
51

The reason for putting include guards in the header file is to prevent the contents of the header from being pulled into a translation unit more than once. That's normal, long-established practice.

The reason for putting redundant include guards in a source file is to avoid having to open the header file that's being included, and back in the olden days that could significantly speed up compilation. These days, opening a file is much faster than it used to be; further, compilers are pretty smart about remembering which files they've already seen, and they understand the include guard idiom, so can figure out on their own that they don't need to open the file again. That's a bit of hand-waving, but the bottom line is that this extra layer isn't needed any more.

EDIT: another factor here is that compiling C++ is far more complicated than compiling C, so it takes far longer, making the time spent opening include files a smaller, less significant part of the time it takes to compile a translation unit.

Wretch answered 19/6, 2017 at 12:28 Comment(5)
Here's a link to back up your "hand-waving" with some documentation ;-) : gcc.gnu.org/onlinedocs/cppinternals/Guard-Macros.htmlObsolesce
@ArneVogel I noticed the docs say: "There must be no tokens outside the controlling #if-#endif pair, but whitespace and comments are permitted." Does 'tokens' include #pragma once?Kibbutznik
@Kibbutznik Yes. Read that sentence as "The only things you can put outside the controlling #if-#endif pair without disabling the optimization are whitespace and comments." But you shouldn't be using #pragma once anyway.Vaivode
well, you could put #pragma once inside the #ifndef/#endif block. However we don't use #pragma once because one of the compilers we use at work doesn't support it.Ergocalciferol
@sh3rifme: #pragma once is not a token but a preprocessor directive. These are also not allowed for the optimization to work, though. However, GCC supports #pragma once, so the optimization and #pragma once are redundant. As Tom Tanner suggested, if you use both #pragma once and include guards, you might as well put the pragma inside the #ifndef/#endif block. In the unlikely case your compiler has the multiple includes optimization but does not support #pragma once, this should have you covered. That said, #include-behavior is notoriously implementation-dependent anyway.Obsolesce
K
22

The only benefit I can come up with is that it outright stops the linker from bothering to find the file.

The linker will not be affected in any way.

It could prevent the pre-processor from bothering to find the file, but if the guard is defined, that means that it has already found the file. I suspect that if the pre-process time is reduced at all, the effect would be quite minimal except in the most pathologically recursively included monstrosity.

It has a downside that if the guard is ever changed (for example due to conflict with another guard), all the conditionals before the include directives must be changed in order for them to work. And if something else uses the previous guard, then the conditionals must be changed for the include directive itself to work correctly.

P.S. __HEADER_A_HPP__ is a symbol that is reserved to the implementation, so it is not something that you may define. Use another name for the guard.

Ketchum answered 19/6, 2017 at 11:34 Comment(3)
Apologies for the confusion with the linker/pre-processor. You say that __HEADER_A_HPP__ is reserved to the implementation, what do you mean by this? Is it specifically using those semantics that's the issue, like math.hpp and __MATH_HPP__?Kibbutznik
@Kibbutznik the standard says that all identifiers that contain two consecutive underscores are reserved to the implementation. There are other reserved identifiers as well. I recommend acquainting yourself with those rules.Ketchum
@sh3rifme: Reserved to the implementation, which may include such usages as automatically defining __HEADER_A_HPP__ when you include header_a.hpp. This of course breaks your header guard, which assumes it's defined only on the second line.Scorpaenid
S
17

Older compilers on more traditional (mainframe) platforms (we're talking mid-2000s here) did not used to have the optimisation described in other answers, and so it really did used to significantly slow down preprocessing time having to re-read header files that have already been included (bearing in mind in a big, monolithic, enterprise-y project you're going to be including a LOT of header files). As an example, I've seen data that indicates a 26-fold speedup for a file with 256 header files each including the same 256 header files on the VisualAge C++ 6 for AIX compiler (which dates from the mid-2000s). This is a rather extreme example but this sort of speed-up does add up.

However, all modern compilers, even on mainframe platforms such as AIX and Solaris, perform enough optimisation for header inclusion that the difference these days really is negligible. Therefore there is no good reason to have these any more.

This does, however, explain why some companies still hang on to the practice, because relatively recently (at least in C/C++ codebase age terms) it was still worthwhile for very large monolithic projects.

Snout answered 19/6, 2017 at 15:29 Comment(3)
I remember having to use an IBM fortran compiler once, and it made snails looks like racing horses. One file took no less than half an hour to compile on quite potent hardware. gfortran did the same job within a fraction of that time. So, maybe IBM compilers are not the best reference for measuring compilation speed. Anyway, on a modern kernel, those 256 header files will still be in the page cache when the compiler attempts to read them, so the 64k opens on the same 256 files should take no more than a second if a syscall is less than 10 microseconds.Expensive
@cmaster not only the opening but the reading - remember the preprocessor has to scan to the last #endifSnout
Which also is still in the page cache. Even if each of the 256 files is 128 kiB in size, that's just 32 MiB of data, totaling to 8 GiB of data that needs to be copied from kernel space to user space. Modern hardware can do that in less than a second. If a compiler takes painfully long on this operation, it's 100% the fault of the compiler.Expensive
A
8

Although there are people arguing against it, in practice '#pragma once' works perfectly and the main compilers (gcc/g++, vc++) support it.

So whatever puristic argumentation people are spreading, it works a lot better:

  1. Fast
  2. No maintenance, no trouble with mysterious non-inclusion because you copied an old flag
  3. Single line with obvious meaning versus cryptic lines spread in file

So simply put:

#pragma once

at the start of the file, and that's it. Optimized, maintainable, and ready to go.

Antonomasia answered 20/6, 2017 at 21:26 Comment(6)
Include guards are not cryptic. Any C(++) programmer who knows what they're doing will immediately understand an include guard, and less experienced ones might even have to look up #pragma once (whereas they'd be fine with a standard include guard). Since all compilers will either optimize the include guard (real compilers) or fail to compile the #pragma once (toy compilers), it's not any faster than a standard include guard either.Mucin
@Mucin But main point is that guards are not error prone, and in 99.9% pragma once is enough which frees you from worrying about this good old know thing. In comparison to pragma once - they are truly cryptic and... slow. ;) Slow just because you need write them, maintain and read.Unbound
@Mucin after 30+ years of using the #ifdef guards I was glad that my colleague Kris figured out that the 'new' pragma once was now supported everywhere. He wrote a script to bulk replace it and although it's just a small thing, this is the sort of thing that makes everybody happy. Thanks again Kris!Antonomasia
@tom can you name a few compilers that don't?Antonomasia
#pragma once was considered for standardisation, but rejected because it cannot be implemented reliablyTailwind
@Tailwind I see your argument, I really do. Look at what Visual C++ is forcing us to do with all the dll_export horror and what not. But pragma once is reliably implemented cross-platform on all the platforms we support and that we will reasonably be supporting at any time. And if it will give problems at any time... well... a script to generate the ifdefs to replace the pragma statement is easily made.Antonomasia

© 2022 - 2024 — McMap. All rights reserved.