Как обнаружить переполнение unsigned целых чисел в C++?
Почему unsigned переполнение — это не UB, но всё равно проблема
В отличие от signed целых, переполнение unsigned в C++ строго определено стандартом: результат вычисляется по модулю 2N, где N — количество бит типа. Это означает, что при сложении UINT_MAX + 1 вы получите 0, а не ошибку. Однако для многих приложений такое "оборачивание" нежелательно, и его нужно детектировать.
Основной подход: проверка до операции
Самый надёжный способ — проверять возможность переполнения до выполнения операции. Это работает для всех арифметических действий и не зависит от поведения после переполнения. Приведу примеры для сложения, вычитания и умножения:
#include <limits>
#include <stdexcept>
// Сложение
bool will_add_overflow(unsigned int a, unsigned int b) {
return a > std::numeric_limits<unsigned int>::max() - b;
}
// Вычитание (переполнение при a < b)
bool will_sub_overflow(unsigned int a, unsigned int b) {
return a < b;
}
// Умножение
bool will_mul_overflow(unsigned int a, unsigned int b) {
if (a == 0 || b == 0) return false;
return a > std::numeric_limits<unsigned int>::max() / b;
}
Использование:
unsigned int safe_add(unsigned int a, unsigned int b) {
if (will_add_overflow(a, b)) {
throw std::overflow_error("unsigned addition overflow");
}
return a + b;
}
Проверка после операции (только для сложения)
Для сложения можно использовать трюк: если результат меньше одного из операндов, значит, было переполнение. Это работает, потому что при оборачивании результат становится меньше любого из исходных чисел (для положительных операндов). Но этот метод не подходит для умножения и вычитания.
unsigned int a = UINT_MAX;
unsigned int b = 1;
unsigned int result = a + b;
if (result < a) { // или result < b
std::cout << "Overflow detected!" << std::endl;
}
Современные компиляторные встроенные функции
GCC и Clang предоставляют встроенные функции для безопасной арифметики, которые возвращают флаг переполнения:
__builtin_add_overflow(a, b, &result)— возвращает true при переполнении__builtin_sub_overflow(a, b, &result)__builtin_mul_overflow(a, b, &result)
Пример:
#include <iostream>
int main() {
unsigned int a = 4294967295;
unsigned int b = 1;
unsigned int result;
if (__builtin_add_overflow(a, b, &result)) {
std::cout << "Overflow occurred!" << std::endl;
} else {
std::cout << "Result: " << result << std::endl;
}
return 0;
}
Эти функции поддерживаются начиная с GCC 5 и Clang 3.8. Для MSVC есть аналогичные _add_overflow, _sub_overflow, _mul_overflow из библиотеки <intrin.h>.
Использование стандартной библиотеки C++17 и C++20
Начиная с C++17, в <numeric> появились функции std::add_sat (насыщающее сложение) и подобные, но они не сообщают о переполнении явно. В C++23 планируется добавление std::add_overflow и других, но пока это не стандартизировано.
Рекомендация для вашего проекта
Используйте комбинацию подходов:
- Для критических мест — проверку до операции (наиболее портативно).
- Если используете GCC/Clang — встроенные функции
__builtin_*_overflow. - Для сложения можно применять проверку
result < aкак быстрый костыль, но помните, что он не работает для умножения.
Также рекомендую рассмотреть использование типов с фиксированной разрядностью из <cstdint> (например, uint32_t) для явного контроля размера.
Альтернативный подход: использование классов-обёрток
Если вы часто работаете с числами, где переполнение критично, можно создать простой класс-обёртку, который автоматически проверяет каждую операцию. Это инкапсулирует логику и делает код чище.
#include <limits>
#include <stdexcept>
template <typename T>
class SafeInt {
T value;
public:
SafeInt(T v) : value(v) {}
SafeInt operator+(const SafeInt& other) const {
if (value > std::numeric_limits<T>::max() - other.value) {
throw std::overflow_error("Overflow in addition");
}
return SafeInt(value + other.value);
}
T get() const { return value; }
};
int main() {
SafeInt<unsigned int> a(4294967295);
SafeInt<unsigned int> b(1);
try {
auto c = a + b; // выбросит исключение
} catch (const std::overflow_error& e) {
std::cerr << e.what() << std::endl;
}
return 0;
}
Такой подход легко расширяется на другие операции (вычитание, умножение) и типы. Минус — небольшое снижение производительности из-за проверок, но для большинства приложений это незаметно.
Также можно использовать библиотеку Boost.SafeNumerics, которая предоставляет готовые безопасные типы с настраиваемой политикой обработки ошибок.
Ответов пока нет.
Войдите чтобы оставить ответ