>
>
>
V1112. Comparing expressions with diffe…


V1112. Comparing expressions with different signedness can lead to unexpected results.

Анализатор обнаружил подозрительное сравнение, в котором типы выражений имеют одинаковый ранг, но разные знаки. При этом ранги типов меньше, чем у '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

По этой причине анализатор не выдаёт срабатывание на приведённом ранее синтетическом примере без этой настройки.

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