Чому дорівнює 0 <-0x80000000?


253

У мене нижче проста програма:

#include <stdio.h>

#define INT32_MIN        (-0x80000000)

int main(void) 
{
    long long bal = 0;

    if(bal < INT32_MIN )
    {
        printf("Failed!!!");
    }
    else
    {
        printf("Success!!!");
    }
    return 0;
}

Умова if(bal < INT32_MIN )завжди вірна. Як це можливо?

Це добре працює, якщо я зміню макрос на:

#define INT32_MIN        (-2147483648L)

Хтось може вказати на це питання?


3
Скільки коштує CHAR_BIT * sizeof(int)?
5gon12eder

1
Ви спробували роздрукувати бал?
Райан Фіцпатрік

10
ІМХО цікавіше те, що це вірно тільки для -0x80000000, але невірно для -0x80000000L, -2147483648і -2147483648L(GCC 4.1.2), тому виникає питання: чому це ІНТ буквальні -0x80000000відрізняється від Int литерала -2147483648?
Андреас Фестер

2
@Bathsheba Я щойно запускаю програму на онлайн-компіляторі tutorialspoint.com/codingground.htm
Jayesh Bhoi

2
Якщо ви коли-небудь помічали, що це (деякі втілення) <limits.h>визначає INT_MINяк (-2147483647 - 1), тепер ви знаєте, чому.
zwol

Відповіді:


363

Це досить тонко.

Кожен цілий літерал у вашій програмі має тип. Який тип він регулює таблиця в 6.4.4.1:

Suffix      Decimal Constant    Octal or Hexadecimal Constant

none        int                 int
            long int            unsigned int
            long long int       long int
                                unsigned long int
                                long long int
                                unsigned long long int

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

  • Спробуйте int
  • Якщо вона не може поміститися, спробуйте long
  • Якщо вона не може поміститися, спробуйте long long.

Hex літерали поводяться по-різному! Якщо літерал не може вписатись у тип підписаного типу int, він спершу спробує, unsigned intперш ніж перейти до спроб великих типів. Дивіться різницю у наведеній вище таблиці.

Тож у 32-бітовій системі ваш буквальний 0x80000000тип має тип unsigned int.

Це означає, що ви можете застосувати одинарний -оператор у прямому рядку без виклику визначеної реалізацією поведінки, як це було б інакше при переповненні підписаного цілого числа. Натомість ви отримаєте значення 0x80000000, позитивне значення.

bal < INT32_MINвикликає звичайні арифметичні перетворення, а результат вираження 0x80000000сприяє переходу unsigned intдо long long. Значення 0x80000000збережене і 0 менше 0x80000000, звідси і результат.

Коли ви замінюєте буквальне, 2147483648Lви використовуєте десяткові позначення, і тому компілятор не вибирає unsigned int, а намагається помістити його всередині long. Також суфікс L говорить, що ви хочете, long якщо це можливо . Суфікс L насправді має подібні правила, якщо ви продовжуєте читати згадану таблицю в 6.4.4.1: якщо число не входить до запитуваного long, чого немає в 32-бітному випадку, компілятор видасть вамlong long де підійдуть просто чудово.


3
"... замінивши буквальне на -2147483648L, ви явно отримаєте довгий, який підписується." Гммм, в 32-бітній longсистемі2147483648L , не буде відповідати в long, тому вона стає long long, то- застосовується - або так я думав.
chux

2
@ASH Тому що максимальне число, яке може мати int, є тоді 0x7FFFFFFF . Спробуйте самі:#include <limits.h> printf("%X\n", INT_MAX);
Лундін

5
@ASH Не плутайте шістнадцяткове представлення цілих літералів у вихідному коді з базовим двійковим поданням підписаного числа. Буквальний, 0x7FFFFFFFколи записаний у вихідному коді, завжди є позитивним числом, але ваша intзмінна може, звичайно, містити необроблені двійкові числа до значення 0xFFFFFFFF.
Лундін

2
@ASH ìnt n = 0x80000000змушує перетворити з непідписаного буквалу в підписаний тип. Що буде, залежить від вашого компілятора - це визначена реалізацією поведінка. У цьому випадку він вирішив показати весь літерал у int, перезаписавши біт знака. В інших системах неможливо представляти тип, і ви посилаєтесь на не визначене поведінку - програма може вийти з ладу. Ви будете мати таку саму поведінку, якщо це зробите, int n=2147483648;це зовсім не пов'язане з шестигранною позначенням.
Лундін

3
Пояснення того, як унар -застосовується до непідписаних цілих чисел, можна трохи розширити. Я завжди припускав (хоча, на щастя, ніколи не покладався на припущення), що непідписані значення будуть "просунуті" до підписаних значень, або, можливо, результат буде невизначений. (Чесно кажучи, це повинна бути помилка компіляції; що це - 3uнавіть означає?)
Кайл Странд

27

0x80000000 є unsigned буквальним значенням 2147483648.

Застосування одинарного мінусу на цьому все ще дає неподписаний тип із ненульовим значенням. (Насправді для ненульового значення x- це значення, яке ви закінчуєте UINT_MAX - x + 1.)


23

Цей цілочисельний літерал 0x80000000має типunsigned int .

Відповідно до стандарту C (6.4.4.1 цілісні константи)

5 Тип цілої константи є першим із відповідного списку, в якому її значення можна представити.

І ця ціла константа може бути представлена ​​типом unsigned int .

Так це вираз

-0x80000000має один і той же unsigned intтип. Більше того, воно має однакове значення 0x80000000в представленні комплементу обох, яке обчислює наступним чином

-0x80000000 = ~0x80000000 + 1 => 0x7FFFFFFF + 1 => 0x80000000

Це має побічний ефект, якщо писати наприклад

int x = INT_MIN;
x = abs( x );

Результат буде знову INT_MIN .

Таким чином, в цьому стані

bal < INT32_MIN

там порівнюється 0з неподписаним значенням0x80000000 перетвореним у тип long long int за правилами звичайних арифметичних перетворень.

Видно, що 0 менше, ніж 0x80000000.


12

Числова константа 0x80000000має тип unsigned int. Якщо ми візьмемо -0x80000000і зробимо 2-х комплімент математику на цьому, ми отримаємо це:

~0x80000000 = 0x7FFFFFFF
0x7FFFFFFF + 1 = 0x80000000

Отже -0x80000000 == 0x80000000. І порівняння (0 < 0x80000000)(оскільки 0x80000000не підписане) - це правда.


Це передбачає 32-розрядні ints. Хоча це дуже поширений вибір, у будь-якій реалізації intможе бути вужчим або ширшим. Однак це правильний аналіз для цього випадку.
Джон Боллінгер

Це не стосується коду ОП, -0x80000000арифметичне без підпису. ~0x800000000інший код.
ММ

Мені здається, найкращу і правильну відповідь мені просто сказати. @MM він роз'яснює, як прийняти доповнення двійки. Ця відповідь конкретно стосується того, що робить негативний знак на число.
Восьминіг

@ Октопод негативний знак не застосовує доповнення 2 до числа (!) Хоча це здається зрозумілим, це не описує те, що відбувається в коді -0x80000000! Насправді доповнення 2 стосується цього питання повністю.
ММ

12

Точка плутанини виникає при мисленні, що -є частиною числової константи.

У наведеному нижче коді 0x80000000- числова константа. Її тип визначають лише за цим. Потім -застосовується і не змінює тип .

#define INT32_MIN        (-0x80000000)
long long bal = 0;
if (bal < INT32_MIN )

Сирі числові константи без запаху є позитивними.

Якщо це десяткове, то тип присвоєний перший типу , який буде тримати його: int, long, long long.

Якщо константа восьмеричний або шістнадцятковий, він отримує перший тип , який тримає його: int, unsigned, long, unsigned long, long long,unsigned long long .

0x80000000, в системі ОП отримує тип unsignedабо unsigned long. Так чи інакше, це якийсь неподписаний тип.

-0x80000000також є деяким ненульовим значенням і, якщо він є непідписаним типом, воно перевищує 0. Коли код порівнює це з a long long, значення не змінюються на двох сторонах порівняння, так 0 < INT32_MINце істинно.


Чергове визначення уникає такої цікавої поведінки

#define INT32_MIN        (-2147483647 - 1)

Давайте ходимо на деякий час по землі фантазії, де intі деunsigned є 48-бітними.

Тоді 0x80000000 вписується intі так є тип int. -0x80000000тоді це від’ємне число, а результат роздруківки відрізняється.

[Назад до реального слова]

Оскільки 0x80000000вписується в якийсь неподписаний тип перед підписаним типом, оскільки він просто більший, ніж some_signed_MAXвсередині some_unsigned_MAX, це якийсь неподписаний тип.


8

C має правило, згідно з яким цілочисельний буквал може бути signedабо unsignedзалежить від того, підходить він signedабо unsigned(ціле просування). На 32-бітній машині 0x80000000буде буквальний unsigned. Доповнення 2 -0x80000000знаходиться 0x80000000 на 32-розрядній машині. Таким чином, порівняння bal < INT32_MINміж signedі unsignedперед порівнянням, згідно з правилом C, unsigned intбуде перетворене на long long.

C11: 6.3.1.8/1:

[...] В іншому випадку, якщо тип операнда з підписаним цілим типом може представляти всі значення типу операнда з непідписаним цілим типом, то операнд з непідписаним цілим типом перетворюється на тип операнда з підписаний цілий тип.

Тому bal < INT32_MINзавжди true.

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