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

«Почему аргументы макроса в C/C++ нужно брать в скобки?» — вопрос из категории C++ Core, который задают на 25% собеседований C/C++ Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Аргументы макроса и всё его тело необходимо заключать в скобки, чтобы гарантировать корректный порядок вычислений после подстановки. Макросы работают на уровне текстовой замены в препроцессоре, не понимая синтаксиса или приоритета операторов C++. Без скобок это приводит к тонким и опасным ошибкам.

Пример проблемы:

#define MULTIPLY_UNSAFE(a, b) a * b
int result1 = MULTIPLY_UNSAFE(2 + 3, 4); // Подстановка: 2 + 3 * 4 = 14 (ожидалось 20)

#define SQUARE_UNSAFE(x) x * x
int result2 = SQUARE_UNSAFE(++value); // Подстановка: ++value * ++value. Неопределённое поведение! Инкремент дважды.

Правильное решение:

#define MULTIPLY_SAFE(a, b) ((a) * (b))
int result3 = MULTIPLY_SAFE(2 + 3, 4); // Подстановка: ((2 + 3) * (4)) = 20

#define SQUARE_SAFE(x) ((x) * (x))
// Но даже с скобками SQUARE_SAFE(++value) останется проблемой из-за двойного инкремента.
// Макросы с побочными эффектами в аргументах — это антипаттерн.

Ключевые правила:

  1. Каждый аргумент в теле макроса заключайте в круглые скобки: (a), (b).
  2. Всё выражение макроса заключайте в круглые скобки: ((a) + (b)).
  3. Избегайте передачи в макросы выражений с побочными эффектами (инкремент, вызов функций). Для таких случаев в современном C++ предпочтительнее использовать inline-функции или constexpr.

Сравнение с inline-функцией (рекомендуемый подход в C++):

template<typename T>
inline T multiply_safe(T a, T b) {
    return a * b;
}
// Нет проблем с приоритетом, есть проверка типов, аргументы вычисляются ровно один раз.
int result = multiply_safe(2 + 3, 4); // OK, возвращает 20