Ответ
Аргументы макроса и всё его тело необходимо заключать в скобки, чтобы гарантировать корректный порядок вычислений после подстановки. Макросы работают на уровне текстовой замены в препроцессоре, не понимая синтаксиса или приоритета операторов 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) останется проблемой из-за двойного инкремента.
// Макросы с побочными эффектами в аргументах — это антипаттерн.
Ключевые правила:
- Каждый аргумент в теле макроса заключайте в круглые скобки:
(a),(b). - Всё выражение макроса заключайте в круглые скобки:
((a) + (b)). - Избегайте передачи в макросы выражений с побочными эффектами (инкремент, вызов функций). Для таких случаев в современном 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 тупо подставит два раза! И переменную увеличит дважды, и результат будет хуй знает какой, потому что это неопределённое поведение — компилятор волен делать что захочет. Кот, сука, собака!
Как делать правильно, чтобы не было мучительно больно: Правило простое, как три копейки: всё, что движется — в скобки!
- Каждый аргумент в скобки:
(a),(b). - Всё выражение целиком тоже в скобки:
((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 функцию — делай это не раздумывая. Сэкономишь себе кучу нервов и времени на отладке какой-нибудь невъебенной хуйни.