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

Препроцессор и макросы

В прошлом уроке #include вкладывал в папку каждого .cpp-файла копию памятки. Эту работу делает не компилятор. До него файлы просматривает секретарь: он выполняет записки с указаниями, а в смысл бумаг не вникает. Одна записка говорит «вложи сюда копию triple.h», другая — «замени везде слово MAX_USERS на 100».

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

Секретарь в C++ — это препроцессор. Он читает файл до компилятора и выполняет директивы — строки, которые начинаются с #. Директива #include заменяет себя текстом указанного файла, слово в слово, ничего в нём не проверяя. Компилятор получает уже готовый текст, в котором директив #include больше нет.

Препроцессор не знает ни типов, ни функций. Он работает только с текстом, и от этого зависит всё, что происходит с макросами.

Макросы

Директива #define создаёт макрос — имя, которое препроцессор заменяет заданным текстом. Это похоже на «Найти и заменить» в текстовом редакторе: замена идёт по тексту, без расчётов. Макрос может обозначать константу, а может принимать параметры, как функция:

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. Аргумент макроса препроцессор не вычисляет: он вставляет его текст на место x как есть и скобок не добавляет. Получившееся выражение компилятор считает по обычному приоритету операторов.

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

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

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

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-функцию:

C++
#include <iostream>

constexpr int half(int x) {
    return x / 2;
}

int main() {
    constexpr int kHalf = half(10 + 4);
    std::cout << kHalf << '\n';  // 7
}

Функция получает аргумент уже вычисленным — 14. Слово constexpr перед функцией разрешает вызывать её при компиляции, когда аргументы — константы. Тогда её результатом можно инициализировать constexpr-переменную, как здесь kHalf.

Главное

  • Препроцессор выполняет директивы до компилятора и работает только с текстом.
  • #define создаёт макрос — имя, которое заменяется текстом без вычислений.
  • Аргумент макроса вставляется как есть, поэтому HALF(10 + 4) превращается в 10 + 4 / 2.
  • Константы и вычисления пишут через constexpr-переменные и constexpr-функции.

Подпишись, чтобы пройти курс

Курс бесплатный. Войди через Telegram и подпишись на канал — откроются все уроки.

Войти другим способом