Перейти к содержимому
Теория · Сборка и препроцессор · урок 2 из 7

#include, #define и защита от повторного включения

Заголовок config.h определяет константу constexpr int kMaxUsers = 100;. Заголовок users.h подключает config.h, а main.cpp подключает оба — и не компилируется:

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

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

Препроцессор

До компилятора файл читает препроцессор (preprocessor) и выполняет директивы — строки, которые начинаются с #. Директива #include заменяет себя текстом указанного файла, слово в слово, ничего в нём не проверяя. В main.cpp текст config.h попадает дважды: напрямую и через users.h. В единице трансляции оказываются два определения kMaxUsers, и компилятор их не принимает.

Макросы

Директива #define создаёт макрос (macro) — имя, которое препроцессор заменяет заданным текстом. Макрос может обозначать константу, а может принимать параметры, как функция:

C++
#include <iostream>

#define MAX_USERS 100
#define SQUARE(x) x * x

int main() {
    std::cout << MAX_USERS << ' ' << SQUARE(7) << '\n';  // 100 49
}

Компилятор получает уже 100 и 7 * 7. Аргумент (argument) макроса препроцессор не вычисляет: он вставляет его текст на место x как есть и скобок не добавляет. Получившееся выражение компилятор считает по обычному приоритету операторов.

Имена макросов пишут заглавными буквами, чтобы их не путали с переменными и функциями. Для констант вместо макросов используют constexpr-переменные: у них есть тип, и их проверяет компилятор.

Защита от повторного включения

Защита от повторного включения (include guard) не даёт тексту заголовка попасть в единицу трансляции дважды. Короче всего её записать строкой #pragma once в начале заголовка:

C++
#pragma once

constexpr int kMaxUsers = 100;

Когда #include встречает этот файл в той же единице трансляции второй раз, препроцессор его пропускает. #pragma once не входит в стандарт C++, но его понимают GCC, Clang и MSVC. Стандартная форма — три директивы: #ifndef CONFIG_H и #define CONFIG_H в начале файла и #endif в конце. При первом включении заголовка макрос CONFIG_H ещё не определён, и текст проходит, а при втором #ifndef пропускает всё до #endif.

Защита действует внутри одной единицы трансляции. Каждый другой .cpp-файл, который подключает заголовок, получает свою копию его текста.

Частая ошибка

Макрос делит аргумент пополам, но результат неверный:

C++
#include <iostream>

#define HALF(x) x / 2

int main() {
    int result = HALF(10 + 4);  // ✗
    std::cout << result << '\n';  // 12
}

Ни компилятор, ни программа об ошибке не сообщают. Препроцессор превратил HALF(10 + 4) в 10 + 4 / 2, а деление выполняется раньше сложения.

Вместо такого макроса напишите функцию: constexpr int half(int x) { return x / 2; }. Функция получает аргумент уже вычисленным — 14. С constexpr вызов функции с константным аргументом может инициализировать и constexpr-переменную: такой вызов компилятор обязан вычислить при компиляции.