Skip to content
Theory · Building and the Preprocessor · lesson 4 of 8

Include guards

Back to the office. One memo sometimes refers to another and asks for a copy of config.h to be attached to it. If a department asks for both memos, the clerk puts config.h into the folder twice—once directly, and once along with the memo that refers to it.

The same thing happens with headers. The header config.h defines the constant constexpr int kMaxUsers = 100;. The header users.h includes config.h, and main.cpp includes both—and does not compile:

C++
#include "config.h"
#include "users.h"  // ✗

int main() {
    return kMaxUsers;
}
TEXT
error: redefinition of 'kMaxUsers'

Where the second definition comes from

The preprocessor inserts text and checks nothing in it. The text of config.h reaches main.cpp twice: directly and through users.h. The translation unit ends up with two definitions of kMaxUsers, and the compiler rejects them. A function declaration can repeat any number of times, but a definition cannot.

Paste one #include at a time and watch how many times the text of config.h reaches main.cpp, then turn a guard on.

LiveWhat #include pastes#include: 0 of 4
files
config.h
users.h
main.cpp
report.cpp
translation units
main.cpp
  1. #include "config.h"waiting
  2. #include "users.h"waiting
  3. int main() {
  4. return kMaxUsers;
  5. }
preprocessor still working
report.cpp
  1. #include "config.h"waiting
  2. int report() {
  3. return kMaxUsers;
  4. }
preprocessor still working
linking comes after compiling

The preprocessor walks the #include lines from top to bottom and puts each file's text in place of the line. Press the button.

Paste one #include at a time, then turn a guard on

Two forms of a guard

An include guard keeps a header's text from entering a translation unit twice. In the office, it is a mark on the folder: "config.h is already inside." A guard comes in two forms.

#pragma once

The shortest guard is a single line at the top of the header:

C++
#pragma once

constexpr int kMaxUsers = 100;

When the preprocessor meets an #include of this file in the same translation unit a second time, it skips the file. #pragma once is not part of the C++ standard, but GCC, Clang and MSVC all support it.

#ifndef, #define and #endif

The standard form is three directives:

  • #ifndef CONFIG_H at the top of the file reads as "if the macro CONFIG_H is not defined";
  • #define CONFIG_H right after it;
  • #endif at the end of the file.
C++
#ifndef CONFIG_H
#define CONFIG_H

constexpr int kMaxUsers = 100;

#endif

The macro CONFIG_H is created with no text: it serves only as a mark that says "the header is already inserted." The preprocessor goes through this file like this:

  1. On the first include, CONFIG_H is not defined yet: #ifndef lets the text through, and #define defines the macro.
  2. On the second include, CONFIG_H is already defined, and #ifndef skips everything up to #endif.

The macro's name comes from the file's name, in capitals, so that two headers never share it.

One translation unit

The guard works within one translation unit. Every other .cpp file that includes the header gets its own copy of its text. The mark sits on one folder, and the other folders do not have it.

Common mistake

The header users.h was made as a copy of config.h, and the macro's name was never changed:

C++
#ifndef CONFIG_H  // ✗
#define CONFIG_H

int count_users();

#endif

The file main.cpp includes config.h first, then users.h, and calls count_users(). By the time the preprocessor reaches users.h, the macro CONFIG_H is already defined, and the whole text of users.h disappears. The compiler finds no declaration:

TEXT
error: use of undeclared identifier 'count_users'

Give every header a macro of its own: USERS_H for users.h.

Key points

  • #include inserts text word for word, so a header can enter a translation unit twice.
  • The compiler rejects a second definition within one translation unit.
  • #pragma once or the trio #ifndef, #define, #endif skips the repeated insertion.
  • A guard works within one translation unit, not across the whole program.

Subscribe to take the course

The course is free. Sign in with Telegram and subscribe to the channel to open every lesson.

Sign in another way