Как работает директива define?cplus-68

Основной механизм работы

Директива #define создает макроподстановку - замену одного фрагмента текста на другой во время фазы препроцессинга. Это чисто текстовая замена, выполняемая до начала компиляции.

Синтаксис:

#define ИМЯ значение      // Простой макрос
#define ИМЯ(параметры) код  // Макрос с параметрами

Типы макросов

1. Простые макросы

#define PI 3.1415926535
#define MAX_SIZE 100
#define DEBUG_MODE

Как работает:

  • Все вхождения PI заменяются на 3.1415926535
  • DEBUG_MODE заменяется на... ничего (используется для #ifdef)

2. Функциональные макросы

#define SQUARE(x) ((x) * (x))
#define MIN(a, b) (((a) < (b)) ? (a) : (b))

Как работает:

  • Аргументы подставляются в тело макроса
  • Важно: каждый параметр и всё тело в скобках!

Особенности подстановки

  1. Многострочные макросы:
#define LOG(msg) \
    std::cout << __FILE__ << ":" << __LINE__ << " " \
              << msg << std::endl
  1. Оператор # (строковый литерал):
#define STRINGIFY(x) #x
// STRINGIFY(hello) → "hello"
  1. Оператор ## (конкатенация токенов):
#define CONCAT(a, b) a##b
// CONCAT(var, 123) → var123

Процесс обработки макросов

  1. Сканирование на наличие идентификаторов макросов
  2. Подстановка аргументов (для функциональных макросов)
  3. Повторное сканирование результата для новых подстановок
  4. Результат передается компилятору

Опасные случаи

  1. Побочные эффекты:
#define SQUARE(x) x * x
int a = 5;
int b = SQUARE(a++); // Становится a++ * a++ - UB!
  1. Проблемы с приоритетом операций:
#define SUM(a, b) a + b
int res = SUM(1, 2) * 3; // 1 + 2 * 3 = 7 (а не 9)
  1. Множественные вычисления:
#define MAX(a, b) ((a) > (b) ? (a) : (b))
MAX(expensiveFunc(), x); // Вызывает expensiveFunc() дважды

Лучшие практики

  1. Всегда брать параметры в скобки:
#define BAD(x) x * 2
#define GOOD(x) ((x) * 2)
  1. Избегать сложных макросов:
  • Вместо них использовать inline-функции
  1. Именование:
  • Использовать ALL_CAPS для макросов
  • Избегать макросов, конфликтующих с идентификаторами

Отладка макросов

  1. Просмотр препроцессированного кода:
g++ -E file.cpp -o file.ii
  1. Предупреждения компилятора:
g++ -Wall -Wextra ...
  1. Директива #error:
#if !defined(MAX_SIZE)
#error "MAX_SIZE must be defined"
#endif

Современные альтернативы

  1. constexpr вместо макросов-констант:
constexpr double PI = 3.1415926535;
  1. inline-функции вместо функциональных макросов:
inline int square(int x) { return x * x; }
  1. constexpr-функции:
constexpr int max(int a, int b) {
    return a > b ? a : b;
}

Пример сложного макроса

#define LOG_IF(cond, msg) \
    do { \
        if (cond) \
            std::cout << __FILE__ << ":" << __LINE__ << " " << msg; \
    } while(0)

// Использование:
LOG_IF(x > 0, "x is positive: " << x);

Почему do-while? Чтобы требовать точку с запятой и избежать проблем с if-else.

Резюмируем: директива #define - мощный, но опасный инструмент препроцессора, выполняющий текстовую подстановку. Хотя современный C++ предлагает более безопасные альтернативы, понимание макросов необходимо для работы с legacy-кодом и некоторыми специальными случаями. Всегда предпочитайте constexpr и inline-функции, где это возможно.