Мы используем куки, чтобы пользоваться сайтом было удобно.
Хорошо
to the top

Вебинар: C++ и неопределённое поведение - 27.02

>
>
>
V1083. Signed integer overflow in arith…
menu mobile close menu
Проверка проектов
Интеграция результатов анализа PVS-Studio в инструменты контроля качества кода (веб дашборд)
Сообщения PVS-Studio
Диагностики общего назначения (General Analysis, C++)
Диагностики общего назначения (General Analysis, C#)
Диагностики общего назначения (General Analysis, Java)
Микрооптимизации (C++)
Диагностика 64-битных ошибок (Viva64, C++)
Реализовано по запросам пользователей (C++)
Cтандарт MISRA
Стандарт AUTOSAR
Стандарт OWASP (C++)
Стандарт OWASP (C#)
Стандарт OWASP (Java)
Проблемы при работе анализатора кода
Дополнительная информация
toggle menu Оглавление

V1083. Signed integer overflow in arithmetic expression. This leads to undefined behavior.

13 Май 2022

Анализатор обнаружил арифметическое выражение, в котором может произойти переполнение знакового числа.

Рассмотрим пример:

long long foo()
{
  long longOperand = 0x7FFF'FFFF;
  long long y = longOperand * 0xFFFF;
  return y;
}

По правилам C и C++ результирующим типом выражения 'longOperand * 0xFFFF' будет 'long'. При использовании компилятора MSVC на Windows тип 'long' имеет размер 4 байта. Максимальное значение, которое может быть представлено этим типом, равно 2'147'483'647 в десятичной системе счисления или 0x7FFF'FFFF в шестнадцатеричной. При умножении переменной 'longOperand' на 0xFFFF (65 535) ожидается результат 0x7FFF'7FFF'0001. Однако согласно стандарту C (см. стандарт С18 пункт 6.5 параграф 5) и C++ (см. стандарт С++20 пункт 7.1 параграф 4) переполнение знаковых чисел приводит к неопределённому поведению.

Исправить этот код можно несколькими способами в зависимости от того, чего хочет программист.

Если требуется произвести корректные вычисления, необходимо использовать типы, размеры которых будут достаточны для отображения чисел. Если число не помещается в машинное слово, то можно воспользоваться одной из библиотек по длинной арифметике. Например, GMP, MPRF, cnl.

Пример выше можно исправить следующим образом:

long long foo()
{
  long longOperand = 0x7FFF'FFFF;
  long long y = static_cast<long long>(longOperand) * 0xFFFF;
  return y;
}

Если переполнение знаковых чисел – это неожидаемое поведение, и его требуется обработать каким-либо образом, то можно воспользоваться специальными библиотеками для безопасной работы с числами. Например, boost::safe_numerics или Google Integers.

Если требуется реализовать циклическую арифметику для знаковых чисел с определённым по стандарту поведением, то для расчётов можно воспользоваться беззнаковыми числами. В случае их переполнения происходит "оборачивание" числа по модулю '2 ^ n', где n – количество бит в числе.

Рассмотрим одно из возможных решений на основе 'std::bit_cast' (C++20):

#include <concepts>
#include <type_traits>
#include <bit>
#include <functional>

namespace detail
{
  template <std::signed_integral R,
            std::signed_integral T1,
            std::signed_integral T2,
            std::invocable<std::make_unsigned_t<T1>,
                           std::make_unsigned_t<T2>> Fn>
  R safe_signed_wrapper(T1 lhs, T2 rhs, Fn &&op)
    noexcept(std::is_nothrow_invocable_v<Fn,
                                         std::make_unsigned_t<T1>,
                                         std::make_unsigned_t<T2>>)
  {
    auto uLhs = std::bit_cast<std::make_unsigned_t<T1>>(lhs);
    auto uRhs = std::bit_cast<std::make_unsigned_t<T2>>(rhs);

    auto res = std::invoke(std::forward<Fn>(op), uLhs, uRhs);

    using UR = std::make_unsigned_t<R>;
    return std::bit_cast<R>(static_cast<UR>(res));
  }
}

Функция 'std::bit_cast' приводит 'lhs' и 'rhs' к соответствующим беззнаковым представлениям. Далее на двух преобразованных операндах выполняется некоторая арифметическая операция. Затем результат расширяется или сужается до нужного результирующего типа и превращается в знаковый.

При таком подходе знаковые числа будут повторять семантику беззнаковых в арифметических операциях, что в свою очередь не будет приводить к неопределённому поведению.

Например, по этой ссылке можно убедиться, что компилятор имеет право оптимизировать код, если видит, что может произойти переполнение знакового числа. Рассмотрим его подробнее:

bool is_max_int(int32_t a)
{
  return a + 1 < a;
}

Если 'a' равно 'MAX_INT', то условие 'a + 1 < a' будет равно 'false'. Таким образом часто проверяют не произошло ли переполнение. Однако компилятор генерирует такой код:

is_max_int(int):                        # @is_max_int(int)
        xor     eax, eax
        ret

Инструкция ассемблера 'xor eax, eax' обнуляет результат выполнения функции 'is_max_int'. В результате последняя всегда возвращает 'true' вне зависимости от значения 'a'. В данном случае это результат неопределённого поведения при переполнении.

В случае применения беззнакового представления такого не происходит:

is_max_int(int):                        # @is_max_int(int)
        cmp     edi, 2147483647
        sete    al
        ret

Компилятор сгенерировал код, который честно проверяет условие.

Выявляемые диагностикой ошибки классифицируются согласно ГОСТ Р 71207–2024 как критические и относятся к типу: Ошибки целочисленного переполнения и некорректного совместного использования знаковых и беззнаковых чисел.

Данная диагностика классифицируется как:

Взгляните на примеры ошибок, обнаруженных с помощью диагностики V1083.

close form

Заполните форму в два простых шага ниже:

Ваши контактные данные:

Шаг 1
Поздравляем! У вас есть промокод!

Тип желаемой лицензии:

Шаг 2
Team license
Enterprise license
** Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности
close form
Запросите информацию о ценах
Новая лицензия
Продление лицензии
--Выберите валюту--
USD
EUR
RUB
* Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности

close form
Бесплатная лицензия PVS‑Studio для специалистов Microsoft MVP
* Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности

close form
Для получения лицензии для вашего открытого
проекта заполните, пожалуйста, эту форму
* Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности

close form
Мне интересно попробовать плагин на:
* Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности

close form
check circle
Ваше сообщение отправлено.

Мы ответим вам на


Если вы так и не получили ответ, пожалуйста, проверьте, отфильтровано ли письмо в одну из следующих стандартных папок:

  • Промоакции
  • Оповещения
  • Спам