Підписання / без підпису порівняння


85

Я намагаюся зрозуміти, чому наступний код не видає попередження у вказаному місці.

//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'
бобах

Це спекуляція, але, можливо, це оптимізація всіх порівнянь, оскільки вона знає відповідь під час компіляції.
Постановка на нуль

2
Ах! повторно Коментар bobah: Я увімкнув усі попередження, і тепер з’являється відсутнє попередження. Я вважаю, що він повинен був виглядати з тим самим рівнем попередження, що й інші порівняння.
Пітер

1
@bobah: Я дуже ненавиджу, що gcc 4.4.2 друкує це попередження (не маючи можливості сказати йому друкувати його лише для нерівності), оскільки всі способи мовчання цього попередження погіршують ситуацію . Просування за замовчуванням надійно перетворює значення -1 або ~ 0 до максимально можливого значення будь-якого непідписаного типу, але якщо ви замовчуєте попередження, подаючи його самостійно, ви повинні знати точний тип. Отже, якщо ви зміните тип (розширте його, скажімо, до unsigned long long), ваші порівняння з голими -1все одно працюватимуть (але ті дають попередження), тоді як ваші порівняння з -1uабо (unsigned)-1обома зазнають невдачі.
Jan Hudec

Я не знаю, навіщо потрібно попередження, і чому компілятори просто не можуть змусити його працювати. -1 від’ємне, тобто менше будь-якого числа без знака. Прості.
CashCow

Відповіді:


95

При порівнянні підписаного з непідписаним компілятор перетворює підписане значення у беззнакове. Для рівності, це не має значення, -1 == (unsigned) -1. Для інших порівнянь це важливо, наприклад , справедливо наступне: -1 > 2U.

EDIT: Список літератури:

5/9: (вирази)

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

  • Якщо один з операндів має тип long double, інший повинен бути перетворений у long double.

  • В іншому випадку, якщо будь-який операнд подвійний, інший повинен бути перетворений у подвійний.

  • В іншому випадку, якщо будь-який операнд є плаваючим, інший повинен бути перетворений у плаваючий.

  • В іншому випадку цілісні акції (4.5) повинні виконуватися на обох операндах. 54)

  • Потім, якщо будь-який операнд не має знака довгий, інший повинен бути перетворений у довгий без знака.

  • В іншому випадку, якщо один операнд - це довгий int, а інший - беззнаковий int, то, якщо довгий int може представляти всі значення беззнакового int, беззнаковий int повинен бути перетворений у довгий int; інакше обидва операнди повинні бути перетворені в беззнакові 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, побітові вони можуть бути рівними, але вони не є логічно рівними.) Я розумію, що якщо примусити це перетворення, воно буде працювати, але чому компілятор це робить? Це нелогічно. Більше того, як я вже коментував вище, коли я з’явив попередження, з’явилося попередження про відсутність ==, яке, схоже, підтверджує те, що я кажу?
Пітер

1
Як кажуть 4.7 / 2, підписаний до беззнакового означає відсутність зміни в бітовій схемі для доповнення двох. Щодо того, чому компілятор робить це, це вимагає стандарт С ++. Я вважаю, що міркуваннями попереджень 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 для недійсного значення дуже поширене з ним.
Jan Hudec

1
Можливо, cpmpilers не повинні робити цього тоді. Якщо він порівнює підписане та безпідписане, просто перевірте, чи не підписане значення від’ємне. Якщо так, то гарантовано буде менше, ніж непідписаного, незалежно.
CashCow

32

Чому підписані / безпідписані застереження важливі, і програмісти повинні зважати на них, демонструє наступний приклад.

Вгадайте результат цього коду?

#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 "Чому він не перетворює відхилення на (більше) значення зі знаком?" Відповідь проста: може бути не більше знакового значення. На 32-бітній машині, у незадовго до того часу, як int, так і long були 32 бітами, і нічого більшого не було. При порівнянні підписаних та безпідписаних найдавніші компілятори C ++ перетворили обидва на підписані. Бо я забуваю, з яких причин комітет зі стандартів С змінив це. Найкраще рішення - якомога більше уникати неподписаних.
James Kanze,

5
@JamesKanze: Я підозрюю, що він також має щось робити з тим фактом, що результатом підписаного переповнення є невизначена поведінка, тоді як результатом безпідписаного переповнення немає, і тому перетворення негативного знакового значення в беззнакове визначається, а перетворення великого беззнакового значення в негативне підписане значення не є .
Jan Hudec,

2
@James Компілятор завжди міг згенерувати збірку, яка реалізувала б більш інтуїтивну семантику цього порівняння, не перекидаючи якийсь більший тип. У цьому конкретному прикладі було б достатньо спочатку перевірити, чи i<0. Тоді iменше, ніж jнапевно. Якщо iне менше нуля, тоді ìйого можна безпечно перетворити на беззнаковий для порівняння j. Звичайно, порівняння між підписаним та безпідписаним було б повільнішим, але їх результат у певному сенсі був би більш правильним.
Свен 02

@Sven Я згоден. Стандарт міг вимагати, щоб порівняння працювали для всіх фактичних значень, замість того, щоб перетворювати їх на один із двох типів. Однак це може працювати лише для порівняння; Я підозрюю, що комітет не хотів різних правил для порівнянь та інших операцій (і не хотів атакувати проблему уточнення порівнянь, коли тип, який фактично порівнювався, не існував).
James Kanze,

4

Оператор == просто виконує побітове порівняння (шляхом простого ділення, щоб перевірити, чи дорівнює воно 0).

Менші / більші порівняння набагато більше покладаються на знак числа.

4-бітний приклад:

1111 = 15? або -1?

тож якщо у вас 1111 <0001 ... це неоднозначно ...

але якщо у вас 1111 == 1111 ... Це те саме, хоча ви не мали на увазі, що це має бути.


Я це розумію, але це не відповідає на моє запитання. Як ви зазначаєте, 1111! = 1111, якщо знаки не збігаються. Компілятор знає, що існує невідповідність типів, так чому він не попереджає про це? (Моя суть полягає в тому, що мій код може містити багато таких невідповідностей, про які мене не попереджають.)
Пітер,

Це те, як воно розроблено. Тест на рівність перевіряє схожість. І це схоже. Я згоден з вами, що так не повинно бути. Ви можете зробити макрос або щось, що перевантажує x == y, щоб бути! ((X <y) || (x> y))
Йочай Таймер

1

У системі, яка представляє значення з використанням 2-комплементу (більшість сучасних процесорів), вони рівні навіть у своїй двійковій формі. Ось чому компілятор не скаржиться на 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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.