Подписанные / неподписанные сравнения
Я пытаюсь понять, почему следующий код не выдает предупреждение в указанном месте.
//from limits.h
#define UINT_MAX 0xffffffff /* maximum unsigned int value */
#define INT_MAX 2147483647 /* maximum (signed) int value */
/* = 0x7fffffff */
int a = INT_MAX;
//_int64 a = INT_MAX; // makes all warnings go away
unsigned int b = UINT_MAX;
bool c = false;
if(a < b) // warning C4018: '<' : signed/unsigned mismatch
c = true;
if(a > b) // warning C4018: '<' : signed/unsigned mismatch
c = true;
if(a <= b) // warning C4018: '<' : signed/unsigned mismatch
c = true;
if(a >= b) // warning C4018: '<' : signed/unsigned mismatch
c = true;
if(a == b) // no warning <--- warning expected here
c = true;
if(((unsigned int)a) == b) // no warning (as expected)
c = true;
if(a == ((int)b)) // no warning (as expected)
c = true;
Я думал, что это связано с фоновым продвижением, но последние два, кажется, говорят иначе.
На мой взгляд, первый ==
сравнение - это такое же несоответствие со знаком или без знака, как и у других?
6 ответов
При сравнении подписанного с неподписанным компилятор преобразует подписанное значение в неподписанное. Для равенства это не имеет значения, -1 == (unsigned) -1
, Для других сравнений это имеет значение, например, верно следующее: -1 > 2U
,
РЕДАКТИРОВАТЬ: Ссылки:
5/9: (выражения)
Многие бинарные операторы, которые ожидают операнды арифметического или перечислимого типа, вызывают преобразования и выдают типы результатов аналогичным образом. Цель состоит в том, чтобы получить общий тип, который также является типом результата. Этот шаблон называется обычными арифметическими преобразованиями, которые определяются следующим образом:
Если один из операндов имеет тип long double, другой должен быть преобразован в long double.
В противном случае, если один из операндов является двойным, другой должен быть преобразован в двойной.
В противном случае, если один из операндов является float, другой должен быть преобразован в float.
В противном случае интегральные преобразования (4.5) должны выполняться для обоих операндов.54)
Затем, если один из операндов является беззнаковым длинным, другой должен быть преобразован в беззнаковый длинный.
В противном случае, если один операнд является длинным int, а другой - без знака int, то, если long int может представлять все значения беззнакового int, беззнаковое int должно быть преобразовано в long int; в противном случае оба операнда должны быть преобразованы в unsigned long int.
В противном случае, если один из операндов длинный, другой должен быть преобразован в длинный.
В противном случае, если один из операндов не подписан, другой должен быть преобразован в беззнаковый.
4.7 / 2: (Интегральные преобразования)
Если тип назначения является беззнаковым, полученное значение является наименьшим целым числом без знака, соответствующим исходному целому числу (по модулю 2n, где n - число битов, используемых для представления типа без знака). [Примечание: в представлении дополнения до двух это преобразование является концептуальным, и в битовой комбинации нет изменений (если нет усечения). ]
EDIT2: уровни предупреждений MSVC
То, о чем предупреждают о различных уровнях предупреждений MSVC, это, конечно, выбор, сделанный разработчиками. На мой взгляд, их выбор в отношении равенства со знаком / без знака по сравнению с большим / меньшим сравнением имеет смысл, это, конечно, совершенно субъективно:
-1 == -1
означает так же, как -1 == (unsigned) -1
- Я считаю, что интуитивный результат.
-1 < 2
не означает то же самое, что -1 < (unsigned) 2
- Это менее интуитивно на первый взгляд, и IMO заслуживает "более раннего" предупреждения.
Почему подписанные / неподписанные предупреждения важны и программисты должны обращать на них внимание, демонстрируется следующим примером.
Угадай вывод этого кода?
#include <iostream>
int main() {
int i = -1;
unsigned int j = 1;
if ( i < j )
std::cout << " i is less than j";
else
std::cout << " i is greater than j";
return 0;
}
Выход:
i is greater than j
Удивлены? Демонстрация в Интернете: http://www.ideone.com/5iCxY
Итог: для сравнения, если один операнд unsigned
тогда другой операнд неявно преобразуется в unsigned
если его тип подписан!
Начиная с C++20 у нас есть специальные функции для корректного сравнения значений со знаком и без знака https://en.cppreference.com/w/cpp/utility/intcmp
Оператор == просто выполняет побитовое сравнение (простым делением, чтобы увидеть, равно ли оно 0).
Чем меньше / больше, чем сравнения, полагаются гораздо больше на знак числа.
4-битный пример:
1111 = 15? или -1?
так что если у вас 1111 < 0001 ... это неоднозначно...
но если у вас есть 1111 == 1111... Это то же самое, хотя вы не хотели, чтобы это было.
В системе, которая представляет значения с использованием 2-дополнения (большинство современных процессоров), они равны даже в двоичной форме. Возможно, поэтому компилятор не жалуется на a == b.
И для меня это странный компилятор не предупреждает вас о == ((int) b). Я думаю, что это должно дать вам предупреждение о целочисленном усечении или что-то в этом роде.
Данная строка кода не генерирует предупреждение C4018, потому что Microsoft использовала другой номер предупреждения (например, C4389) для обработки этого случая, а C4389 не включен по умолчанию (например, на уровне 3).
Из документации Microsoft для C4389:
// C4389.cpp
// compile with: /W4
#pragma warning(default: 4389)
int main()
{
int a = 9;
unsigned int b = 10;
if (a == b) // C4389
return 0;
else
return 0;
};
Другие ответы довольно хорошо объяснили, почему Microsoft, возможно, решила сделать особый случай из оператора равенства, но я считаю, что эти ответы не очень полезны без упоминания C4389 или того, как включить его в Visual Studio.
Я также должен упомянуть, что если вы собираетесь включить C4389, вы также можете рассмотреть возможность включения C4388. К сожалению, официальной документации для C4388 нет, но, похоже, она появляется в следующих выражениях:
int a = 9;
unsigned int b = 10;
bool equal = (a == b); // C4388