Как безопасно проверить переполнение
Вопрос
Как проверить, что a + b или a * b для std::int64_t не переполнится? Расскажите несколько способов и их плюсы и минусы.
Попробуйте сами
Ответьте вслух за 2 минуты, как на собесе. Потом сверьтесь с тезисами: что прозвучало, а что нет.
Показать ответ и разбор
Коротко
Что должно прозвучать в ответе
Проверка до операции: `b > 0 && a > MAX - b` и аналог для отрицательных
Встроенные функции `__builtin_add_overflow`, `__builtin_mul_overflow` в GCC и Clang
Вычисление в более широком типе (`__int128`) и сравнение с границами
Нельзя проверять после операции: переполнение уже UB
Для умножения проверка делением: `a > MAX / b` с аккуратной обработкой знаков
Что могут спросить следом
- Почему
if (a + b < a)неправильная проверка? - Что выбрать в переносимом коде без расширений GCC?
Разбор
Главное правило: проверять нужно до операции, потому что само переполнение знакового уже UB, и проверка вида a + b < a может быть удалена оптимизатором. Для сложения переносимо так: если b > 0, переполнение будет при a > INT64_MAX − b, если b < 0, при a < INT64_MIN − b. Для умножения аналогично через деление, но там много случаев со знаками и с −1 · INT64_MIN. На GCC и Clang удобнее встроенные функции __builtin_add_overflow(a, b, &res) и __builtin_mul_overflow: они возвращают флаг и компилируются в инструкцию с проверкой флага переполнения, это быстро и без ошибок. Третий вариант: посчитать в более широком типе, для 64 бит это __int128, и сравнить с границами. Он простой, но тоже не стандартный. В переносимом коде я бы обернул проверку в функцию с предусловиями и покрыл тестами на границах, а в коде под GCC и Clang использовал встроенные функции.
Ответьте сами, а не прочитайте
В тренажёре вопрос попадёт в повторения и вернётся, когда начнёт забываться. Плюс билеты с зачётом: 20 вопросов, не больше 2 ошибок.
Ещё по теме «Типы и неопределённое поведение»
- Порядок байтов: перевернуть uint32 и посчитать единицы
- Умножение int64 с проверкой переполнения
- Середина двух int64 без переполнения
- std::bit_cast
- Что C++20 изменил для знаковых целых
- Порядок вычисления аргументов функции
- Объясни, что такое неопределённое поведение
- Продвижение unsigned char