#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!

Your subscription could not be saved. Please try again.
Your subscription has been successful.

Don't miss out!

Subscribe to IncludeGuardian updates for more future content, ways to improve your C/C++ build times, and the release of our CI! We will use your email address to send you information on IncludeGuardian only and you are free to unsubscribe at any time.