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


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.