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

87

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

//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;

Я думал, что это связано с фоновым продвижением, но последние два, кажется, говорят иначе.

На мой взгляд, первое ==сравнение - это такое же несоответствие знак / без знака, как и другие?

Питер
источник
3
gcc 4.4.2 выводит предупреждение при вызове с '-Wall'
bobah
Это предположение, но, возможно, он оптимизирует все сравнения, поскольку знает ответ во время компиляции.
Null Set
2
Ах! ре. Комментарий Боба: Я включил все предупреждения, и теперь появляется отсутствующее предупреждение. Я считаю, что он должен был появиться на том же уровне предупреждения, что и другие сравнения.
Питер,
1
@bobah: Я действительно ненавижу, что gcc 4.4.2 печатает это предупреждение (без возможности указать ему, чтобы он печатал только из-за неравенства), поскольку все способы заглушить это предупреждение ухудшают ситуацию . Продвижение по умолчанию надежно преобразует как -1, так и ~ 0 в максимально возможное значение любого беззнакового типа, но если вы отключите предупреждение, произнеся его самостоятельно, вы должны знать точный тип. Поэтому, если вы измените тип (расширите его, скажем, на unsigned long long), ваши сравнения с голым по- -1прежнему будут работать (но они дают предупреждение), в то время как ваши сравнения с -1uили (unsigned)-1оба будут с треском провалиться.
Ян Худек
Я не знаю, почему вам нужно предупреждение и почему компиляторы просто не могут заставить его работать. -1 отрицательно, поэтому меньше любого беззнакового числа. Простые.
CashCow

Ответы:

96

При сравнении подписанного с беззнаковым компилятор преобразует подписанное значение в беззнаковое. Для равенства это не имеет значения -1 == (unsigned) -1. Для других сравнений это имеет значение, например, верно следующее:-1 > 2U .

РЕДАКТИРОВАТЬ: Ссылки:

5/9: (Выражения)

Многие бинарные операторы, которые ожидают операндов арифметического или перечислительного типа, вызывают преобразования и выдают типы результатов аналогичным образом. Цель состоит в том, чтобы получить общий тип, который также является типом результата. Этот шаблон называется обычными арифметическими преобразованиями, которые определяются следующим образом:

  • Если один из операндов имеет тип long double, другой должен быть преобразован в long double.

  • В противном случае, если один из операндов является двойным, другой должен быть преобразован в двойной.

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

  • В противном случае интегральные акции (4.5) должны выполняться для обоих операндов. 54)

  • Затем, если один из операндов является беззнаковым длинным, другой должен быть преобразован в беззнаковый длинный.

  • В противном случае, если один операнд является длинным int, а другой - unsigned int, тогда, если длинный int может представлять все значения беззнакового int, беззнаковый int должен быть преобразован в длинный int; в противном случае оба операнда должны быть преобразованы в unsigned long int.

  • В противном случае, если один из операндов является длинным, другой должен быть преобразован в длинный.

  • В противном случае, если один из операндов беззнаковый, другой должен быть преобразован в беззнаковый.

4,7 / 2: (Интегральные преобразования)

Если целевой тип беззнаковый, результирующее значение является наименьшим беззнаковым целым числом, совпадающим с исходным целым числом (по модулю 2 n, где n - количество битов, используемых для представления беззнакового типа). [Примечание: в представлении с дополнением до двух это преобразование является концептуальным, и битовый шаблон не изменяется (если нет усечения). ]

EDIT2: уровни предупреждений MSVC

То, о чем предупреждают о различных уровнях предупреждений MSVC, конечно же, сделано разработчиками. Насколько я понимаю, их выбор в отношении равенства со знаком / без знака и сравнений больше / меньше имеет смысл, это, конечно, полностью субъективно:

-1 == -1означает то же самое, что и -1 == (unsigned) -1- я считаю, что это интуитивный результат.

-1 < 2 не означает то же самое, что и -1 < (unsigned) 2- На первый взгляд это менее интуитивно понятно, и ИМО заслуживает «более раннего» предупреждения.

Эрик
источник
Как можно преобразовать подписанное в неподписанное? Что такое беззнаковая версия подписанного значения -1? (знаковый -1 = 1111, тогда как без знака 15 = 1111, побитовое равенство, но они не равны логически.) Я понимаю, что если вы принудительно выполните это преобразование, оно сработает, но зачем компилятору это делать? Это нелогично. Более того, как я прокомментировал выше, когда я включил предупреждения, появилось предупреждение missing ==, которое, похоже, подтверждает то, что я говорю?
Питер
1
Как сказано в 4.7 / 2, знаковое преобразование в беззнаковое означает отсутствие изменения битовой комбинации для дополнения до двух. Что касается того, почему компилятор делает это, это требуется стандартом C ++. Я считаю, что причиной предупреждений VS на разных уровнях является вероятность того, что выражение будет непреднамеренным - и я согласен с ними, что сравнение равенства подписанных / неподписанных «менее вероятно» будет проблемой, чем сравнения неравенств. Это, конечно, субъективно - это выбор разработчиков компилятора VC.
Эрик
Хорошо, думаю, я почти понял. Как я это читал, компилятор (концептуально) делает: 'if (((unsigned _int64) 0x7fffffff) == ((unsigned _int64) 0xffffffff))', потому что _int64 - наименьший тип, который может представлять как 0x7fffffff, так и 0xffffffff без подписи?
Питер,
2
На самом деле по сравнению с (unsigned)-1или -1uчасто хуже, чем по сравнению с -1. Это потому что (unsigned __int64)-1 == -1, но (unsigned __int64)-1 != (unsigned)-1. Поэтому, если компилятор выдает предупреждение, вы пытаетесь отключить его, приводя к беззнаковому или используя, -1uи если значение на самом деле оказывается 64-битным или вы случайно измените его на одно позже, вы сломаете свой код! И помните, что size_tэто беззнаковый, 64-битный только на 64-битных платформах, и использование -1 для недопустимого значения очень часто с ним.
Ян Худек
1
Может быть, тогда cpmpilers не должны этого делать. Если он сравнивает знаковое и беззнаковое, просто проверьте, является ли значение со знаком отрицательным. Если да, то в любом случае гарантированно будет меньше беззнакового.
CashCow
33

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

Угадайте вывод этого кода?

#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 если его тип подписан!

Наваз
источник
2
Он прав! Это глупо, но он прав. Это серьезная ошибка, с которой я никогда раньше не сталкивался. Почему он не преобразует беззнаковое значение в (большее) знаковое значение ?! Если вы сделаете «if (i <((int) j))», он будет работать так, как вы ожидаете. Хотя "if (i <((_int64) j))" имеет больше смысла (при условии, что вы не можете, что _int64 вдвое больше int).
Питер
6
@Peter "Почему он не конвертирует unsgiend в (большее) знаковое значение?" Ответ прост: не может быть большего значения со знаком. В скором времени на 32-битной машине и int, и long были 32-битными, и не было ничего больше. При сравнении подписанного и неподписанного, самые ранние компиляторы C ++ конвертировали оба в подписанные. Я забыл, по каким причинам комитет по стандартам C. Лучшее решение - по возможности избегать использования без знака.
Джеймс Канце,
5
@JamesKanze: я подозреваю, что это также должно что-то делать с тем фактом, что результатом подписанного переполнения является неопределенное поведение, в то время как результат переполнения без знака - нет, и поэтому преобразование отрицательного значения со знаком в беззнаковое определяется при преобразовании большого значения без знака в отрицательный знак значение нет .
Ян Худек
2
@James Компилятор всегда может сгенерировать сборку, которая реализует более интуитивную семантику этого сравнения без приведения к более крупному типу. В этом конкретном примере достаточно сначала проверить, есть ли i<0. Тогда iменьше чем jнаверняка. Если iне меньше нуля, то ìего можно безопасно преобразовать в беззнаковый для сравнения j. Конечно, сравнения подписанных и неподписанных будут медленнее, но их результат в некотором смысле будет более правильным.
Sven
@Sven Я согласен. Стандарт мог потребовать, чтобы сравнения работали для всех фактических значений вместо преобразования в один из двух типов. Однако это будет работать только для сравнений; Я подозреваю, что комитет не хотел других правил для сравнений и других операций (и не хотел атаковать проблему определения сравнений, когда фактически сравниваемый тип не существует).
Джеймс Канце
4

Оператор == просто выполняет побитовое сравнение (простым делением, чтобы узнать, равен ли он 0).

Сравнение меньше / больше зависит от знака числа.

4-битный пример:

1111 = 15? или -1?

так что если у вас 1111 <0001 ... это неоднозначно ...

но если у вас есть 1111 == 1111 ... Это то же самое, хотя вы этого не имели в виду.

Йохай Тиммер
источник
Я понимаю это, но это не отвечает на мой вопрос. Как вы заметили, 1111! = 1111, если знаки не совпадают. Компилятор знает о несоответствии типов, так почему он не предупреждает об этом? (Я хочу сказать, что мой код может содержать много таких несоответствий, о которых меня не предупреждают.)
Питер,
Так оно и было. Тест на равенство проверяет сходство. И похоже. Я согласен с вами, что так быть не должно. Вы можете сделать макрос или что-то такое, что перегружает x == y, чтобы быть! ((X <y) || (x> y))
Йохай Тиммер
1

В системе, которая представляет значения с использованием двух дополнений (большинство современных процессоров), они равны даже в своей двоичной форме. Возможно, поэтому компилятор не жалуется на a == b .

И мне кажется странным, что компилятор не предупреждает вас об a == ((int) b) . Я думаю, это должно дать вам предупреждение об усечении целого числа или что-то в этом роде.

Хоссейн
источник
1
Философия C / C ++ заключается в следующем: компилятор полагает, что разработчик знает, что (я) делает при явном преобразовании между типами. Таким образом, предупреждения нет (по крайней мере, по умолчанию - я считаю, что есть компиляторы, которые генерируют предупреждения для этого, если уровень предупреждения установлен выше, чем по умолчанию).
Péter Török,
0

Данная строка кода не генерирует предупреждение 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
Тим Рэй
источник