← Назад к вопросам

Почему аргументы макроса нужно брать в скобки?

2.2 Middle🔥 171 комментариев
#Язык C++

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI30 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Почему аргументы макроса нужно брать в скобки?

Краткий ответ

Скобки вокруг аргументов макроса защищают от ошибок приоритета операторов и неожиданного поведения при подстановке текста. Без них простой макрос может привести к логическим ошибкам и очень сложно заметить баги.

Суть проблемы

Макросы работают путём текстовой подстановки перед компиляцией. Компилятор просто заменяет имя макроса его определением, подставляя аргументы вместо параметров. Это может привести к неожиданным результатам, если не использовать скобки.

Пример без скобок

// Плохо — БЕЗ скобок
#define SQUARE(x) x * x

int a = 5;
int result = SQUARE(a + 1);  // Что здесь произойдёт?

Макрос развернётся так:

int result = a + 1 * a + 1;  // = a + a + 1 = 11, а не 36!

Мы ожидали (a+1)² = 36, но получили a+a+1 = 11 из-за приоритета операторов!

Правильно — с внутренними скобками

// Хорошо — скобки вокруг аргумента
#define SQUARE(x) ((x) * (x))

int result = SQUARE(a + 1);  // = ((a+1) * (a+1)) = 36 ✓

Второй подвох — приоритет самого макроса

// Плохо
#define DOUBLE(x) x + x

int result = 5 * DOUBLE(3);  // Ожидаем 30, получаем?

Разворачивается как:

int result = 5 * 3 + 3;  // = 18, а не 30!

Правильно:

#define DOUBLE(x) ((x) + (x))

int result = 5 * DOUBLE(3);  // = 5 * ((3) + (3)) = 30 ✓

Третий подвох — множественное использование аргумента

// Опасный макрос
#define INCREMENT(x) (x++)

int a = 5;
int result = INCREMENT(a) + INCREMENT(a);

Это раскроется как:

int result = (a++) + (a++);

Проблема: a инкрементируется дважды, что часто не является намерением. Если же использовать:

int result = INCREMENT(++a) + INCREMENT(++a);

То a инкрементируется четыре раза — это undefined behavior!

Правила использования скобок в макросах

1. Скобки вокруг каждого аргумента:

#define ABS(x) (((x) < 0) ? (-(x)) : (x))
#define MIN(a, b) (((a) < (b)) ? (a) : (b))

2. Скобки вокруг всего макроса:

#define ADD(a, b) ((a) + (b))
#define MULTIPLY(a, b) ((a) * (b))

3. Если аргумент используется несколько раз — это ещё более проблемно:

// ОЧЕНЬ ПЛОХО
#define SWAP(a, b) { int tmp = a; a = b; b = tmp; }
int x = 1, y = 2;
SWAP(x++, y++);  // x и y инкрементируются ДВАЖДЫ!

// Лучше использовать inline функцию или std::swap
template <typename T>
inline void swap(T& a, T& b) {
    T tmp = a; a = b; b = tmp;
}

Практические примеры правильных макросов

// Логирование
#define LOG(msg) std::cout << "[LOG] " << (msg) << std::endl

// Получение размера массива
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))

// Assert с сообщением
#define ASSERT(cond, msg) \
    if (!(cond)) { \
        std::cerr << "Assertion failed: " << (msg) << std::endl; \
        std::exit(1); \
    }

// MAX/MIN
#define MAX(a, b) (((a) > (b)) ? (a) : (b))
#define MIN(a, b) (((a) < (b)) ? (a) : (b))

Когда лучше НЕ использовать макросы

В современном C++ существуют безопасные альтернативы:

// Вместо макроса
#define SQUARE(x) ((x) * (x))

// Используй constexpr функцию
constexpr int square(int x) { return x * x; }

// Или шаблон
template <typename T>
T square(T x) { return x * x; }

Constexpr и шаблоны проверяются компилятором, имеют типизацию и не подвержены ошибкам подстановки.

Заключение

Скобки в макросах — это необходимая защита от ошибок приоритета операторов при текстовой подстановке. Всегда оборачивайте:

  • Каждый аргумент в скобки
  • Весь результат макроса в скобки
  • Тело макроса (многострочное) в скобки do-while

Лучший совет: по возможности избегайте макросов и используйте constexpr функции или шаблоны вместо них.