Попередження - порівняння між цілими виразами зі знаком та без знака


80

На даний момент я працюю над Accelerated C ++ і натрапив на проблему під час вправи 2-3.

Короткий огляд програми - програма в основному бере назву, а потім відображає привітання в рамках зірочок - тобто Привіт! оточений обрамлений *.

Вправа - У прикладі програми автори використовують const intдля визначення заповнення (пробіли) між привітанням та зірочками. Потім вони просять читача, як частину вправи, попросити користувача ввести, наскільки великим вони хочуть бути прокладкою.

Все це здається досить простим, я продовжую запитувати у користувача два цілих числа ( int) і зберігаю їх і змінюю програму на використання цілих чисел, вилучаючи ті, що використовуються автором, під час компіляції, хоча я отримую таке попередження;

Вправа2-3.cpp: 46: попередження: порівняння між підписаними та беззнаковими цілими виразами

Після деяких досліджень здається, що код намагається порівняти одне з вищезазначених цілих чисел ( int) з a string::size_type, що чудово. Але мені було цікаво - чи означає це, що я повинен змінити одне з цілих чисел на unsigned int? Чи важливо чітко вказати, чи є мої цілі числа підписаними чи непідписаними?

 cout << "Please enter the size of the frame between top and bottom you would like ";
 int padtopbottom;
 cin >> padtopbottom;

 cout << "Please enter size of the frame from each side you would like: ";
 unsigned int padsides; 
 cin >> padsides;

 string::size_type c = 0; // definition of c in the program
 if (r == padtopbottom + 1 && c == padsides + 1) { // where the error occurs

Вище знаходяться відповідні біти коду, cце типу, string::size_typeтому що ми не знаємо, як довго може бути привітання - але чому я отримую цю проблему зараз, коли авторський код не отримав проблему під час використання const int? На додаток - кожному, хто, можливо, закінчив прискорений C ++ - чи буде це пояснено далі в книзі?

Я працюю на Linux Mint, використовуючи g ++ через Geany, якщо це допомагає чи робить різницю (оскільки я читав, що це може при визначенні, що string::size_typeє).


2
чи не можна припустити, що ви все одно захочете підписати ints? Я не можу придумати логічної причини, чому верх і низ повинні бути негативними
Woot4Moo

Це правда, і я згадав про це у дописі вище, але я все ще не розумію, чому ця проблема не сталася у прикладі програми автора, коли вони використовували const int? Я впевнений, що доберусь до цього в книзі, але не можу не цікавитись.
Тім Гаррінгтон,

Покиньте це - очевидно, що в цій ситуації це не дало попередження, тому що int завжди був 1 ... ой.
Тім Гаррінгтон,

1
Загалом, збільшення діапазону не варте клопоту з використанням unsignedінтегральних типів для відліків. Непідписані номери також мають гарантовану поведінку, що робить їх незначно менш ефективними.
Джон Перді

4
Можливо, автор бачив те саме попередження і просто проігнорував його. Не припускайте, що автори книг є більш обізнаними або обережними, ніж середній програміст.
Крістофер Джонсон,

Відповіді:


104

Це, як правило , хороша ідея , щоб оголосити змінні, unsignedабо , size_tякщо вони будуть по порівнянні з розмірами, щоб уникнути цієї проблеми. По можливості використовуйте точний тип, з яким порівнюватимете (наприклад, використовуйте std::string::size_typeпри порівнянні з std::stringдовжиною ').

Компілятори попереджають про порівняння типів підписаних та беззнакових, оскільки діапазони підписаних та непідписаних входів різні, і коли їх порівнюють між собою, результати можуть бути дивовижними. Якщо вам потрібно зробити таке порівняння, вам слід явно перетворити одне зі значень у тип, сумісний з іншим, можливо, після перевірки, щоб переконатись, що перетворення є дійсним. Наприклад:

unsigned u = GetSomeUnsignedValue();
int i = GetSomeSignedValue();

if (i >= 0)
{
    // i is nonnegative, so it is safe to cast to unsigned value
    if ((unsigned)i >= u)
        iIsGreaterThanOrEqualToU();
    else
        iIsLessThanU();
}
else
{
    iIsNegative();
}

11
Я знаю, що сучасний стандарт C іноді вимагає, щоб від’ємні знакові значення порівнювали більші, ніж непідписані, але чи слід вважати будь-які ситуації, коли це трапляється, застарілими? Я хотів би, щоб стандарти еволюціонували, щоб принаймні дозволити компіляторам виробляти арифметично правильну поведінку (це означає, що якщо підписане значення від'ємне, воно порівнюється меншим, а якщо беззнакове значення перевищує максимальне значення підписаного типу, воно порівнює більше ). Здається дивним, що компілятори зобов'язані створювати шалену поведінку за відсутності явних наборів типів.
supercat

4
@supercat: Оскільки цілочисельні порівняння компілюються до однієї машинної інструкції, а для будь-якого тестування або обробки крайових випадків потрібні кілька машинних інструкцій, те, що ви пропонуєте, швидше за все, не буде додано як функцію C ... це, звичайно, не може бути поведінка за замовчуванням, оскільки це без потреби призведе до зниження продуктивності, навіть якщо програміст знає, що це не потрібно.
Блейк Міллер

@BlakeMiller: Код, який хоче порівняти підписане та беззнакове значення, як ніби обидва непідписані, можуть створити одне і запустити "на повній швидкості". В іншому випадку, у багатьох випадках різниця буде між порівнянням і переходом, приймаючи дві інструкції проти трьох, що було б дешевше, ніж код, який вручну обробляв різні випадки.
supercat

1
@BlakeMiller: (Причина, по якій я кажу два проти трьох, полягає в тому, що більшість кодів, які порівнюють два числа, використовуватимуть одну інструкцію для виконання порівняння та встановлення прапорів на їх основі; у багатьох випадках компілятор міг би організувати речі так, щоб до порівняння, прапор "знак" містив би верхній біт одного з операндів, тому одного умовного стрибка перед порівнянням було б достатньо для забезпечення правильної семантики). Зверніть увагу, що оскільки існує безліч способів досягти правильної семантики, компілятор може вибрати той, який з них можна зробити найдешевше. Написати код C для правильної семантики було б складніше.
supercat

6
Тільки для демонстрації того, що "результати можуть здивувати", наступна програма (після вставки #include <cstdio>вгорі ... і я використовую g ++ 4.4.7), надрукує "true", зазначивши, що це правда, що (підписано) -1 більше, ніж (без підпису) 12: int main(int, char**) { int x = -1; unsigned int y = 12; printf("x > y: %s\n", x > y ? "true":"false"); return 0; }
villapx

9

У мене була точно така сама проблема вчора, працюючи над проблемою 2-3 в Accelerated C ++. Головне - змінити всі змінні, які ви будете порівнювати (за допомогою булевих операторів), на сумісні типи. У цьому випадку це означає string::size_type(або unsigned int, але, оскільки в цьому прикладі використовується перший, я просто дотримуватимусь цього, хоча ці два технічно сумісні).

Зверніть увагу, що в оригінальному коді вони зробили саме це для лічильника c (сторінка 30 у розділі 2.5 книги), як ви справедливо зазначили.

Що ускладнює цей приклад, це те, що різні змінні доповнення (padadsides і padtopbottom), а також усі лічильники також повинні бути змінені на string::size_type.

Переходячи до вашого прикладу, код, який ви опублікували, в кінцевому підсумку виглядатиме так:

cout << "Please enter the size of the frame between top and bottom";
string::size_type padtopbottom;
cin >> padtopbottom;

cout << "Please enter size of the frame from each side you would like: ";
string::size_type padsides; 
cin >> padsides;

string::size_type c = 0; // definition of c in the program

if (r == padtopbottom + 1 && c == padsides + 1) { // where the error no longer occurs

Зверніть увагу, що в попередньому умовному повідомленні ви отримаєте помилку, якщо не ініціалізували змінну r як a string::size_typeу forциклі. Отже, вам потрібно ініціалізувати цикл for, використовуючи щось на зразок:

    for (string::size_type r=0; r!=rows; ++r)   //If r and rows are string::size_type, no error!

Отже, в основному, як тільки ви вводите string::size_typeзмінну в мікс , щоразу , коли ви хочете виконати логічну операцію над цим елементом, усі операнди повинні мати сумісний тип для компіляції без попереджень.


6

Важливою відмінністю між підписаними та беззнаковими ints є інтерпретація останнього біта. Останній біт у підписаних типах представляє знак числа, що означає: наприклад:

0001 - це 1 підпис і без підпису 1001 - -1 підпис і 9 без підпису

(Я уникнув усього випуску доповнення для ясності пояснень! Це не зовсім те, як ints представлені в пам'яті!)

Ви можете собі уявити, що важливо знати, якщо порівнювати з -1 або з +9. У багатьох випадках програмісти просто лінуються оголосити підрахунок ints як непідписані (здуття головки циклу for fi). Це, як правило, не є проблемою, оскільки з ints вам доводиться рахувати до 2 ^ 31, поки ваш розрядний біт вас не вкусить. Тому це лише попередження. Тому що нам лінь писати замість "int" слово "без підпису".


Ах, розумію - зараз я змінив підрахунок int як непідписаний. Це вважається хорошою практикою чи навіть поганою практикою? :)
Тім Гаррінгтон,

Будь ласка, якщо ви проти, коротко поясніть, чому. Навіть якщо це лише одне слово. Я не бачу нічого поганого у своїй відповіді. Ви можете мені допомогти, що може бути проблемою.
AndreasT

1
@Tim: "unsigned" - це синонім "unsigned int". Ви повинні використовувати непідписаний тип int або стандартну змінну підрахунку / ітерації stl std :: size_t (що також є синонімом). Кращою практикою є використання беззнаку у всіх випадках "перебору елементів від 0 до n". Це покращує чіткість і видаляє попередження, тому стає переможцем ;-)
AndreasT

9
Внутрішнє представлення підписаних цілих чисел залежить від компілятора (тобто машинного). Ваше позначення зі знаковим бітом не використовується широко через деякі проблеми (+/- нуль - одна з них). Більшість машин використовують поняття доповнення двох для представлення від’ємних чисел. Перевага полягає в тому, що може використовуватися і звичайна (непідписана) арифметика без будь-яких змін. -1 у понятті доповнення 2 буде 1111 btw.
sstn

1
@AndreasT: хоча зрозуміло "уникати всієї проблеми доповнення для ясності", ви могли б скористатися прикладом, сумісним із доповненням 2, представленням, яке використовується практично на всіх платформах. 1001для -1 було поганим вибором, набагато кращим вибором було б " 1111дорівнює -1 підписано і 15 без підпису"
MestreLion

4

На крайніх діапазонах непідписаний int може стати більшим, ніж int.
Тому компілятор генерує попередження. Якщо ви впевнені, що це не проблема, сміливо призначайте типи до того ж типу, щоб попередження зникло (використовуйте привід C ++, щоб їх було легко помітити).

Крім того, зробіть змінні однакового типу, щоб зупинити скаржник компілятора.
Я маю на увазі, чи можна мати негативне заповнення? Якщо так, то зберігайте його як внутрішній. В іншому випадку вам слід скористатися непідписаним int і дозволити потоку вловлювати ситуації, коли користувач вводить від’ємне число.



0

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

8-біт: -1 підпис - це ті самі біти, що і 255 без підпису 16-біт: -1 підпис - це ті самі біти, що і 65535 без підпису тощо.

Отже, якщо у вас є такий код:

int fd;
fd = open( .... );

int cnt;
SomeType buf;

cnt = read( fd, &buf, sizeof(buf) );

if( cnt < sizeof(buf) ) {
    perror("read error");
}

Ви виявите, що якщо виклик read (2) не вдасться через дескриптор файлу, який стає недійсним (або якась інша помилка), для cnt буде встановлено значення -1. При порівнянні з sizeof (buf), значенням без підпису, оператор if () буде хибним, оскільки 0xffffffff не менше, ніж sizeof () деякої (обґрунтованої, не придуманої, щоб мати максимальний розмір) структуру даних.

Таким чином, ви повинні написати вище, якщо, щоб видалити підписане / безпідписане попередження як:

if( cnt < 0 || (size_t)cnt < sizeof(buf) ) {
    perror("read error");
}

Це просто голосно говорить про проблеми.

1.  Introduction of size_t and other datatypes was crafted to mostly work, 
    not engineered, with language changes, to be explicitly robust and 
    fool proof.
2.  Overall, C/C++ data types should just be signed, as Java correctly
    implemented.

Якщо у вас є настільки великі значення, що ви не можете знайти підписаний тип значення, який працює, ви використовуєте занадто малу кількість процесора або занадто велику величину значень у вибраній мовою. Якщо, як і в грошах, кожна цифра має значення, є системи, якими можна користуватися на більшості мов, які забезпечують вам нескінченні цифри точності. C / C ++ просто не робить цього добре, і ви повинні бути дуже чіткими щодо всього навколо типів, як згадувалося в багатьох інших відповідях тут.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.