Подписанные / неподписанные сравнения

Я пытаюсь понять, почему следующий код не выдает предупреждение в указанном месте.

//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
Другие вопросы по тегам