Почему аргументы макроса в 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

Ответ 18+ 🔞

А, ну это классика, ёпта! Слушай, тут про макросы в C++ речь, и если ты их неправильно оформишь — будет тебе хиросима и нигерсраки, честное слово.

Вот смотри, в чём прикол. Макросы — они же тупые как пробка, реально. Это не функции, которые умные и всё понимают. Это просто текстовая замена, как в старом добром блокноте. Препроцессор берёт и тупо подставляет один кусок текста на место другого, а потом уже компилятор пытается это осмыслить. И вот тут-то и начинается пиздец, если ты не поставил скобки.

Пример, где всё летит в пизду:

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

Видишь? Ожидал ты, что (2+3) умножится на 4, получится 20. А препроцессор сделал подстановку, и получилось 2 + 3 * 4. А умножение-то, сука, приоритетнее сложения! Вот и выходит 14. Овердохуища разницы, да? Программа будет работать, но выдавать какую-то дичь, и ты потом будешь голову ломать, где ошибка.

А вот это вообще пиздец, волнение ебать:

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

Представляешь? Он ++value тупо подставит два раза! И переменную увеличит дважды, и результат будет хуй знает какой, потому что это неопределённое поведение — компилятор волен делать что захочет. Кот, сука, собака!

Как делать правильно, чтобы не было мучительно больно: Правило простое, как три копейки: всё, что движется — в скобки!

  1. Каждый аргумент в скобки: (a), (b).
  2. Всё выражение целиком тоже в скобки: ((a) * (b)).

Вот смотри на исправленный вариант:

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

Теперь, ёпта, порядок вычислений жёстко зафиксирован. Сначала (2+3), потом умножение на (4). Красота!

Но даже тут есть засада, ядрёна вошь:

#define SQUARE_SAFE(x) ((x) * (x))
// Да, скобки есть, но SQUARE_SAFE(++value) всё равно сосалка, потому что ++value подставится два раза.

Поэтому главное правило номер два: НИКОГДА не суй в макросы выражения с побочными эффектами (типа ++i, func() которая что-то меняет). Доверия к ним — ноль ебать. Это прямой путь в ад.

А что делать по-современному, чтобы не бздеть? В C++ уже дохуя лет как есть нормальные inline-функции и шаблоны. Они тебе и типы проверят, и аргументы вычислят ровно один раз, и приоритет операторов сами знают. Рай и благодать!

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

Короче, чувак, вывод простой: если уж юзаешь макросы (хотя лучше бы не надо), обкладывай их скобками как сумасшедший. А если можно заменить на inline функцию — делай это не раздумывая. Сэкономишь себе кучу нервов и времени на отладке какой-нибудь невъебенной хуйни.