C++ Header Guards: #ifndef vs #pragma once and Portability

Key takeaways

A header guard makes the preprocessor skip a header's contents the second time it is included in the same translation unit. #ifndef/#define/#endif is standard and portable; #pragma once is shorter and supported by all major compilers. Neither prevents multiple-definition link errors across .cpp files, and a copy-pasted guard macro silently hides a whole header.

The error guards exist to prevent

C++ has no real notion of importing a file. #include pastes the file’s text into the current source file before compilation, and the result (one .cpp plus everything it pulled in) is a translation unit. If the same header gets pasted into one translation unit twice, everything in it is defined twice.

That happens more easily than it sounds. Suppose shape.h includes point.h, and main.cpp includes both:

// point.h (no guard)
struct Point { int x, y; };

// shape.h
#include "point.h"
struct Shape { Point origin; };

// main.cpp
#include "point.h"
#include "shape.h"   // pulls in point.h a second time
int main() { Shape s{}; return s.origin.x; }

GCC stops with:

point.h:1:8: error: redefinition of 'struct Point'
point.h:1:8: note: previous definition of 'struct Point'

Nobody wrote Point twice, and main.cpp is not wrong for including a header it uses directly. The fix belongs in point.h: it has to be safe to include any number of times. That property is what a header guard provides.

To see how a header ended up included twice, g++ -H (Clang supports it too) prints the include tree, one dot per nesting level:

. point.h
. shape.h
.. point.h

#ifndef guards

#ifndef MYPROJECT_GEOMETRY_POINT_H
#define MYPROJECT_GEOMETRY_POINT_H

struct Point { int x, y; };

#endif  // MYPROJECT_GEOMETRY_POINT_H

The first time the preprocessor reads the file, the macro is not defined, so it defines it and processes the body. The second time, #ifndef is false and everything up to the matching #endif is skipped. This uses nothing but standard preprocessor directives, so it works with every C and C++ compiler ever made.

A common worry is that the compiler still has to open and scan the file the second time. GCC’s preprocessor documents an optimization for exactly this idiom: when the whole file is wrapped in an #ifndef/#endif pair with nothing outside it except comments, it remembers the controlling macro and does not reread the file if the macro is still defined. Other major compilers do something similar. So the speed argument between guards and #pragma once is, in practice, not worth weighing; the number of headers you include matters far more.

Naming the macro

The guard macro lives in the same global namespace as every other macro in the program, so it has to be unique across the project and all its dependencies. Generic names are where things go wrong. Here are two unrelated headers that both use the obvious name:

// net/utils.h
#ifndef UTILS_H
#define UTILS_H
inline int netPort() { return 80; }
#endif

// str/utils.h
#ifndef UTILS_H
#define UTILS_H
inline int trimLen() { return 0; }
#endif

// main.cpp
#include "net/utils.h"
#include "str/utils.h"   // UTILS_H already defined: whole file skipped
int main() { return netPort() + trimLen(); }
main.cpp:3:33: error: 'trimLen' was not declared in this scope

The error points at the use, not at the guard, and the header visibly declares trimLen, which is why this one can take a while to spot. The same thing happens when a new header is created by copying an old one and the guard is not renamed; it compiles until the day both are included together.

Base the name on the project and the file’s path, for example MYPROJECT_NET_UTILS_H (the Google C++ style guide uses the form <PROJECT>_<PATH>_<FILE>_H_). Avoid names that start with an underscore followed by an uppercase letter, or that contain a double underscore, such as _UTILS_H or __UTILS_H__. Those identifiers are reserved for the implementation, and using them risks colliding with the standard library’s own guards.

Clang also warns when the #define does not match the #ifndef it follows (a typo like #ifndef POINT_H / #define PIONT_H), which would otherwise make the guard useless: 'POINT_H' is used as a header guard here, followed by #define of a different macro. clang-tidy’s llvm-header-guard check can enforce path-based names across a code base.

#pragma once

#pragma once

struct Point { int x, y; };

Instead of a macro, the compiler records the file itself and refuses to process it again within the translation unit. There is no name to choose, so there is nothing to collide or to forget to rename. GCC, Clang, MSVC and the Intel compilers all support it.

The catch is in the phrase “the file itself”. The standard does not define #pragma once, and each compiler decides for itself when two include paths refer to the same file. The case that bites in practice is two copies of the same header reachable through different paths: a vendored copy of a library in the source tree and an installed copy under /usr/include, or the same directory mounted twice in a build container. To #pragma once those are different files, so both get processed and you are back to redefinition of .... With #ifndef, both copies use the same macro and the second is skipped, which hides the duplication instead of reporting it but at least compiles. Symlinks and network file systems are the other area where compilers have historically disagreed about file identity.

SituationReasonable choice
Application code built with GCC, Clang or MSVC#pragma once
Library distributed as source to unknown toolchains#ifndef
Headers that may exist as multiple copies on the include path#ifndef (and fix the include path)
Existing code baseWhatever it already uses, consistently

Some projects put both in every header. It is harmless, but it also brings back the macro-naming problem that #pragma once was supposed to remove.

What guards do not protect against

This is the misunderstanding I run into most with header guards: the belief that a guarded header can contain anything. A guard works inside one translation unit. Each .cpp file is compiled separately, starts with no macros defined, and processes the header fresh.

// util.h
#ifndef UTIL_H
#define UTIL_H
int helper() { return 42; }   // a definition, not inline
#endif

// a.cpp
#include "util.h"
int a() { return helper(); }

// b.cpp
#include "util.h"
int a();
int main() { return helper() + a(); }

Both files compile. The link fails:

multiple definition of `helper()'; ...a.cpp:(.text+0x0): first defined here

The guard did its job in each translation unit; the problem is that a.o and b.o each contain a definition of helper, which violates the One Definition Rule. The fixes are to mark the function inline (which allows identical definitions in several translation units), make it a template, or leave only the declaration int helper(); in the header and move the body to util.cpp. The same applies to non-const global variables in headers; since C++17, inline int counter = 0; is the header-safe form.

Guards and circular includes

Guards also change what a circular include looks like. Without them, a.h including b.h including a.h would recurse until the compiler gives up on include depth. With guards, the recursion stops, but one of the headers ends up seeing the other before its contents have been processed:

// a.h
#ifndef A_H
#define A_H
#include "b.h"
struct A { B* b; };
#endif

// b.h
#ifndef B_H
#define B_H
#include "a.h"     // A_H already defined: skipped, A not declared yet
struct B { A a; };
#endif

Including a.h from a .cpp file gives:

b.h:4:12: error: 'A' does not name a type

The message is confusing precisely because a.h is included right above. The second time around, the guard skipped it, and struct A had not been reached yet. The real fix is to break the cycle. A only needs a pointer to B, so a.h can replace #include "b.h" with a forward declaration struct B;, and the cycle disappears. When both sides genuinely need the full definition of the other by value, the design itself is circular and one of the types has to hold the other through a pointer or reference.

Where modules fit

C++20 modules replace textual inclusion for code that uses them: an import does not paste text, macros defined in a module do not leak out, and importing twice is naturally harmless, so module interface units do not need guards. Ordinary headers, including those you still #include from inside a module’s global module fragment, still need them. For most code bases, guards will be around for a long time.