#pragma once
vs #ifndef
Include guards are a core part of C/C++ programming. There are two idiomatic methods
to prevent a file from being included multiple times within the same translation
unit: #pragma once
and a guard macro using #ifndef
. This
article will dissect these two methods, highlighting their differences and equipping
you with the necessary knowledge to select the most suitable include guard
for your project.
Why do we need Include Guards?
Include guards help to prevent the contents of a header file from being included repeatedly within a translation unit, a situation arising when multiple included files each themselves include a common header. Without these guards, we would need to carefully curate what we include in a header file to avoid violating the One-Definition Rule (ODR). Even if we only included elements that could appear repeatedly, such as function declarations, this would still create unnecessary work for the preprocessor and the compiler front-end due to handling duplicate code.
Guard Macro
The canonincal form of a include guard using a guard macro is:
- Comments and whitespace only
#ifndef MACRO_NAME
#define MACRO_NAME
- Your code
#endif
- Comments and whitespace only
The first time this file is included, the macro MACRO_NAME
has
not yet been defined. The #ifndef
passes, we define our macro
and the contents of the file is included. The next time this file is
included within the same translation unit, the macro is (hopefully) still
defined, the #ifndef
fails and the content is skipped by the
preprocessor.
#pragma once
#pragma once
is a non-standard extension that is supported by
the vast majority of compilers. To use #pragma once
, just put
the directive anywhere within your header file as long as it is not skipped
over with conditional inclusion, e.g. using #if 0
. The first
time a #pragma once
is encountered in a file, the compiler
marks this file and will skip over it anytime it is subsequently included in
the current translation unit.
Comparison
We will look at ease of use, correctness, and performance for both guard macros
and #pragma once
.
Ease of Use
Both guard macros and #pragma once
are idiomatic and the
presence of either should not be confusing for developers.
A guard macro requires a unique macro name per file that won't clash with any other macros used in not only your project, but all libraries you depend on and will depend on in the future. A clash may result in non-sensical compilation errors that change depending on the order of inclusion. These clashes are simple to fix as long as they occur in code you own. If you are unlucky to have clashes across multiple third-party libraries, you will have to fix this upstream and/or modify your local copy. This may not be simple if you are using package managers that require additional effort to maintain a separate copy.
Typically the guard macro will be prefixed with INCLUDE
and the
name of your project to reduce the chance of clashes with other projects. To
avoid clashes internally the macro needs to include elements of the path and
filename (e.g. INCLUDE_MYPROJECT_UTILS_STRINGHELPER
). The downside
is that guard macros must be updated when files are moved or renamed,
increasing the maintenance burden.
Alternatively, guard macros can be a UUID (e.g. INCLUDE_6B2903E0_43F4_4A64_8DF0_B99ECE067C71
) which,
if generated properly, will all-but-guarantee uniqueness and will
never need to change, even if files are moved or renamed. The
main downside is that it is harder to review new code to catch UUID reuse
without having to search your code base for the UUID, since it is common
that developers copy an existing header file for new files and may forget to
generate a new UUID.
Performance will be covered in a later section, but it is worth mentioning
that guard macros have more strict requirements to allow them to be enabled
for the multiple-include optimization. For example, MSVC
does not optimise #if !defined(MACRO_NAME)
. This adds another
possibility of writing a guard that prevents multiple inclusion, but is not
optimised for build speed on some or all platforms. For further details,
please refer to our article on Include Guards and their
Optimizations.
In reality, clashes in guard macros are rare if they are constructed as
described above, but the numerous ways a developer can make a mistake with
guard macros means that #pragma once
is the winner for
ease of use.
Correctness
#pragma once
is a compiler extension and not required to be
implemented by conforming C/C++ compilers. However, at this point it has
been so widely supported for such a long time that lack of support
is not a consideration by the vast majority of projects. If you
are an exception to the rule then most likely you already know about this
restriction by the compilers you are targeting.
With guard macros you assign a unique identifier to all of your header files and this determines whether files are the same. When using #pragma once the burden of deciding whether two files are the same falls to the compiler.
Although this may sound simple, it is far harder to determine than necessary and there is disagreement by compilers on how to do this:
- GCC considers two files to be the same if and only if they have the same file contents and the same modification time (bug report)
- Clang considers two files to be the same if and only if they have the same inode/fileID and device ID or they are symlinks to the same inode
- MSVC considers two files to be the same if and only if they have file paths that case-insensitively compare equal
These tests can be run yourself with the Pragma Once Issues Test.
In particular there are several interesting situations in which the main compilers differ:
- Symlinks/Hardlinks - whether two files that are symlinks/hardlinks of one another are considered equal
- Duplicate content - whether a file with the same contents are considered equal
- Duplicate content + mtime - whether a file with the same contents and modification time are considered equal
- Caseless - whether two files with paths that compare equal case-insensitively are considered equal
Below is whether the compiler considers the files in the above situation as equal.
Clang | GCC | MSVC | |
---|---|---|---|
Symlink | yes | yes | no |
Hardlink | yes | yes | no |
Duplicate contents | no | no | no |
Duplicate content + mtime | no | yes | no |
Caseless | no | no | yes |
If you want consistency across these compilers you'll need to avoid these discrepancies both when architecting your projects and the build system you use. If you are avoiding symlinks/hardlinks and having files with file paths that differ only in their case, then the only remaining rare issue that could arise is having different files have the same contents and modification time in GCC.
It is also worth mentioning an outstanding bug in GCC when using
#pragma once
in a file that starts with a UTF-8 byte-order
mark (BOM) and is included within a precompiled header (bug
report).
The consistency and simplicity of macro guards has a slight
advantage over #pragma once
, though it is easy
to restrict yourself to a subset of features so that #pragma
once
will behave identically across compilers.
Performance
Both guard macros and #pragma once
benefit from the
multiple-include optimization, where compilers avoid
opening and preprocessing a properly guarded header file after it has been
initially seen. As noted previously, a guard macro needs to be of a
particular form in order to benefit and this is covered in depth in the Include Guards and Their
Optimizations.
It is impossible to have headers that use guard macros and self-include
benefit from the multiple-include optimization. You either have to use
#pragma once
, or create an additional guarded header
specifically for public consumption. This public header should only include
the original (now made private) header that self-includes. You either have
to use. See this pull request to
Boost Preprocessor for more details.
It is worth noting that there is an outstanding bug in GCC where having
thousands of include directives in a single file, each pointing to a
different file that is guarded with #pragma once, will incur an additional
overhead (bug
report). This is an issue with the implementation and not an
underlying problem with #pragma once
.
As both situations above are exceptional cases, I'll declare this a
draw between guard macros and #pragma once
.
Final Thoughts
First, if you are targeting Clang, GCC, and MSVC, there is rarely a need
to use both macro guards and #pragma once
. If your
macro guards are strict enough to trigger the multiple-include optimization,
then you can remove #pragma once
and have the same build
performance with no change in behavior. In fact, trying to combine the two can
create problems, see this issue with EASTL's
eassert.h where both methods are attempted and the result is a file that
is not enabled for the multiple-include optimization.
Though macro guards are more error prone to implement, once you manage to choose a globally unique macro name and write the guard correctly, there are no future ways it can go wrong. It is resilient against duplicate files, symlinks, hardlinks etc. If you are a library writer, have an existing complicated build system, or are not particularly worried about developers making mistakes, then macro guards may be better for you.
#pragma once
is simpler and almost impossible to get
wrong, but there are rare cases in which the compiler may
incorrectly determine whether two files are equal or not. If these cases arise,
they can be fixed painlessly by swapping to a macro guard for affected files. If
you don't anticipate any of these issue then using #pragma once
will remove almost all developer mistakes.
Remember that consistency is king. Choose one style over the other, document it, and make sure that all your headers are as consistent as possible.
Finally, use the free IncludeGuardian tool to check whether all of your header files are properly guarded and do so in a way that enabled the multiple-include optimization to keep your builds fast!
To catch issues automatically, watch out for the release of IncludeGuardian's GitHub CI to check your include guards on each pull request. Sign up below to avoid missing out!