Анализатор обнаружил подозрительное сравнение, в котором типы выражений имеют одинаковый ранг, но разные знаки. При этом ранги типов меньше, чем у 'int'. Из-за неявного преобразования таких выражений к типу 'int' или 'unsigned int' сравнение может приводить к неожиданным результатам.
Рассмотрим синтетический пример:
bool foo(char lhs, unsigned char rhs)
{
return lhs == rhs; // <=
}
В примере присутствует сравнение переменных с типами разной знаковости: 'lhs' типа 'char' и 'rhs' типа 'unsigned char'. Будем считать, что нижележащий тип 'char' — это 'signed char' (например, на архитектуре x86_64). Тип 'unsigned char' может отразить диапазон значений от [0 .. 255], при этом тип 'char' – [-128 .. 127]. Согласно стандартам C и C++, перед сравнением значений переменных должно произойти неявное преобразование типов (integral promotion), в результате которого и может возникнуть проблема.
Компилятор превращает код со сравнением в следующий:
return (int) lhs == (int) rhs;
Такое преобразование он делает, если тип 'int' может отобразить диапазон значений 'char' и 'unsigned char', иначе вместо 'int' выберется 'unsigned int'. На большинстве современных платформ тип 'int' занимает 4 байта и с лёгкостью может отобразить эти диапазоны.
В случае, если 'lhs' имел отрицательное значение, то оно же и сохранится в результате преобразования в левом операнде. При этом значение правого операнда после преобразования 'rhs' всегда будет неотрицательным, т.к. исходный тип был беззнаковым. В итоге результат сравнения будет вычисляться как 'false'. Возможна и обратная ситуация. Если в переменной 'rhs' находится значение в диапазоне [128 .. 255], то в этой ситуации результат сравнения будет также 'false'.
Такая ошибка может неожиданно проявить себя при смене компилятора или настроек, когда ранее тип 'char' был беззнаковым, а стал знаковым, и наоборот. Например, в таком случае при вызове функции 'foo' с аргументами '\xEE' и '\xEE' будет считаться, что переданы неравные значения. Хотя такое поведение логично с точки зрения стандарта, оно может быть неожиданным для разработчика.
Ошибки можно избежать двумя способами.
Способ N1. Преобразовать выражения к общему типу по знаку:
if ((unsigned char) lhs == rhs)
Способ N2. Воспользоваться семейством функций 'std::cmp_*' (С++20) или их аналогами для сравнения выражений, типы которых имеют различную знаковость:
if (std::cmp_equal(lhs, rhs))
Примечание. В диагностическом правиле реализованы несколько исключений, которые добавлены для уменьшения количества ложных срабатываний. Анализатор выдаёт срабатывание лишь в том случае, если он смог доказать, что диапазон значений одного из операндов не может быть отображён типом другого операнда. Если же требуется выявить все места в коде, где происходит такое сравнение операндов, разных по знаковости, можно воспользоваться следующим комментарием:
//+V1112, ENABLE_ON_UNKNOWN_VALUES
По этой причине анализатор не выдаёт срабатывание на приведённом ранее синтетическом примере без этой настройки.
Выявляемые диагностикой ошибки классифицируются согласно ГОСТ Р 71207–2024 как критические и относятся к типу: Ошибки целочисленного переполнения и некорректного совместного использования знаковых и беззнаковых чисел. |
Данная диагностика классифицируется как: