Dlaczego 0 <-0x80000000?

253

Mam poniżej prosty program:

#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;
}

Warunek if(bal < INT32_MIN )jest zawsze spełniony. Jak to jest możliwe?

Działa dobrze, jeśli zmienię makro na:

#define INT32_MIN        (-2147483648L)

Czy ktoś może wskazać problem?

Jayesh Bhoi
źródło
3
Ile to jest CHAR_BIT * sizeof(int)?
5gon12eder,
1
Próbowałeś wydrukować bal?
Ryan Fitzpatrick,
10
IMHO bardziej interesujące jest to, że jest to prawdziwe tylko dla -0x80000000, ale za fałszywe -0x80000000L, -2147483648a -2147483648L(gcc 4.1.2), więc pytanie brzmi: dlaczego jest int dosłowne -0x80000000różni się od int dosłownym -2147483648?
Andreas Fester,
2
@Bathsheba Właśnie uruchamiam program na kompilatorze online tutorialspoint.com/codingground.htm
Jayesh Bhoi
2
Jeśli kiedykolwiek zauważyłeś, że (niektóre inkarnacje) <limits.h>definiuje INT_MINjako (-2147483647 - 1), teraz wiesz, dlaczego.
zwolnienie

Odpowiedzi:

363

To jest dość subtelne.

Każdy literał całkowity w twoim programie ma swój typ. Jaki typ ma regulowany jest tabelą w 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

Jeśli dosłowna liczba nie zmieści się w inttypie domyślnym , spróbuje użyć następnego większego typu, jak wskazano w powyższej tabeli. Tak więc w przypadku zwykłych liczb całkowitych dziesiętnych wygląda to tak:

  • Próbować int
  • Jeśli to nie pasuje, spróbuj long
  • Jeśli to nie pasuje, spróbuj long long.

Hex literały zachowują się jednak inaczej! Jeśli literał nie zmieści się w podpisanym typie int, najpierw spróbuje, unsigned intzanim przejdzie do wypróbowania większych typów. Zobacz różnicę w powyższej tabeli.

Tak więc w systemie 32-bitowym twój literał 0x80000000jest typowy unsigned int.

Oznacza to, że możesz zastosować jednoargumentowy -operator do literału bez wywoływania zachowania zdefiniowanego w implementacji, tak jak w przypadku przepełnienia liczby całkowitej ze znakiem. Zamiast tego otrzymasz wartość 0x80000000, wartość dodatnią.

bal < INT32_MINwywołuje zwykłe konwersje arytmetyczne, a wynik wyrażenia 0x80000000jest promowany od unsigned intdo long long. Wartość 0x80000000zostaje zachowana, a 0 jest mniejsze niż 0x80000000, stąd wynik.

Kiedy zamieniasz literał na 2147483648L, użyj notacji dziesiętnej i dlatego kompilator nie wybiera unsigned int, ale raczej próbuje dopasować go do long. Również przyrostek L mówi, że chcesz, long jeśli to możliwe . Sufiks L faktycznie ma podobne zasady, jeśli nadal czytasz wspomnianą tabelę w 6.4.4.1: jeśli liczba nie mieści się w żądanym long, co nie jest w przypadku 32-bitowym, kompilator da ci miejsce, w long longktórym będzie dobrze pasować.

Lundin
źródło
3
„... zamieniając literał na -2147483648L, wyraźnie otrzymujesz długi, który jest podpisany.” Hmmm, W 32-bitowym longsystemie 2147483648L, nie zmieści się w sposób long, dzięki czemu staje się long long, następnie- stosowana jest - przynajmniej tak mi się wydawało.
chux - Przywróć Monikę
2
@ASH Ponieważ wtedy maksymalna liczba może być int 0x7FFFFFFF. Spróbuj sam:#include <limits.h> printf("%X\n", INT_MAX);
Lundin,
5
@ASH Nie myl szesnastkowej reprezentacji literałów całkowitych w kodzie źródłowym z podstawową reprezentacją binarną podpisanej liczby. Dosłowność 0x7FFFFFFFzapisana w kodzie źródłowym jest zawsze liczbą dodatnią, ale twoja intzmienna może oczywiście zawierać nieprzetworzone liczby binarne do wartości 0xFFFFFFFF.
Lundin,
2
@ASH ìnt n = 0x80000000wymusza konwersję z niepodpisanego literału na typ podpisany. To, co się stanie, zależy od twojego kompilatora - jest to zachowanie zdefiniowane w implementacji. W tym przypadku postanowił pokazać cały literał w int, zastępując bit znaku. W innych systemach może nie być możliwe przedstawienie tego typu i wywołanie niezdefiniowanego zachowania - program może ulec awarii. Otrzymasz takie samo zachowanie, jeśli to zrobisz, nie int n=2147483648;będzie to wcale związane z notacją heksadecymalną.
Lundin,
3
Wyjaśnienie, w jaki sposób jednoargumentowane -jest stosowane do liczb całkowitych bez znaku, można nieco rozszerzyć. Zawsze zakładałem (choć na szczęście nigdy nie opierałem się na założeniu), że niepodpisane wartości będą „promowane” do wartości podpisanych, a być może wynik będzie niezdefiniowany. (Szczerze mówiąc, powinien to być błąd kompilacji; co to w - 3uogóle oznacza?)
Kyle Strand
27

0x80000000jest unsignedliterałem o wartości 2147483648.

Zastosowanie jednokierunkowego minus na tym nadal daje typ bez znaku z wartością niezerową. (W rzeczywistości, dla wartości niezerowej, wartość x, którą się kończy to UINT_MAX - x + 1.)

Batszeba
źródło
23

Ten literał całkowity 0x80000000ma typ unsigned int.

Według normy C (6.4.4.1 Stałe całkowite)

5 Typ stałej całkowitej jest pierwszym z odpowiedniej listy, na której można przedstawić jego wartość.

Ta stała całkowita może być reprezentowana przez typ unsigned int.

Więc to wyrażenie

-0x80000000ma ten sam unsigned inttyp. Ponadto ma tę samą wartość 0x80000000w reprezentacji uzupełnienia dwóch, która oblicza w następujący sposób

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

Ma to efekt uboczny, jeśli na przykład piszesz

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

Wynik będzie znowu INT_MIN.

Tak więc w tym stanie

bal < INT32_MIN

porównuje się go 0z wartością bez znaku0x80000000 konwertowaną na typ long long int zgodnie z regułami zwykłych konwersji arytmetycznych.

Oczywiste jest, że 0 jest mniejsze niż 0x80000000.

Vlad z Moskwy
źródło
12

Stała liczbowa 0x80000000jest typu unsigned int. Jeśli weźmiemy -0x80000000i wykonamy na nim 2s komplement matematyki, otrzymamy to:

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

Tak -0x80000000 == 0x80000000. A porównywanie (0 < 0x80000000)(ponieważ 0x80000000jest niepodpisane) jest prawdziwe.

dbush
źródło
To zakłada 32-bitowe ints. Chociaż jest to bardzo powszechny wybór, w każdej implementacji intmoże być węższy lub szerszy. Jest to jednak poprawna analiza dla tego przypadku.
John Bollinger,
Nie dotyczy to kodu OP, -0x80000000jest arytmetyką bez znaku. ~0x800000000to inny kod.
MM
To wydaje mi się najlepszą i poprawną odpowiedzią. @MM wyjaśnia, jak wziąć uzupełnienie dwójki. Ta odpowiedź dotyczy w szczególności tego, co robi znak ujemny dla liczby.
Octopus,
@Octopus znak ujemny nie stosuje dopełnienia 2 do liczby (!) Chociaż wydaje się to jasne, nie opisuje tego, co dzieje się w kodzie -0x80000000! W rzeczywistości uzupełnienie 2 nie ma znaczenia dla tego pytania.
MM
12

Punktem zamieszania jest myślenie, że -jest częścią stałej liczbowej.

W poniższym kodzie 0x80000000jest stała liczbowa. Jego typ określa się tylko na tym. -Nakłada potem i nie zmienia typ .

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

Surowe, nie ozdobione stałe liczbowe są dodatnie.

Jeśli jest dziesiętny, a następnie typ przypisany jest pierwszy typ, który będzie posiadać: int, long, long long.

Jeśli stała jest ósemkowym lub szesnastkowym, robi pierwszy typ, który trzyma go: int, unsigned, long, unsigned long, long long, unsigned long long.

0x80000000, w systemie OP pobiera typ unsignedlub unsigned long. Tak czy inaczej, jest to jakiś niepodpisany typ.

-0x80000000jest również pewną niezerową wartością, a ponieważ jest pewnym typem bez znaku, jest większa niż 0. Gdy kod porównuje to do a long long, wartości nie są zmieniane po 2 stronach porównania, więc 0 < INT32_MINjest to prawda.


Alternatywna definicja pozwala uniknąć tego dziwnego zachowania

#define INT32_MIN        (-2147483647 - 1)

Chodźmy w krainie fantasy przez chwilę, gdzie inti unsignedsą 48-bitowe.

Następnie 0x80000000pasuje inti taki jest typ int. -0x80000000jest wtedy liczbą ujemną, a wynik wydruku jest inny.

[Powrót do prawdziwego słowa]

Ponieważ 0x80000000pasuje do jakiegoś typu bez znaku przed typem ze znakiem, ponieważ jest tylko większy niż some_signed_MAXwewnątrz some_unsigned_MAX, jest to pewien typ bez znaku.

chux - Przywróć Monikę
źródło
8

C ma zasadę, że literał całkowity może być signedlub unsignedzależy od tego, czy pasuje do signedczy unsigned(promocja liczb całkowitych). Na 32maszynie -bitowej literał 0x80000000będzie unsigned. Uzupełnienie 2 -0x80000000znajduje się 0x80000000 na komputerze 32-bitowym. Dlatego porównanie bal < INT32_MINjest pomiędzy signedi unsignedprzed porównaniem zgodnie z regułą C unsigned intzostanie przekonwertowane na long long.

C11: 6.3.1.8/1:

[...] W przeciwnym razie, jeśli typ argumentu z typem całkowitym ze znakiem może reprezentować wszystkie wartości typu argumentu z typem całkowitym bez znaku, wówczas argument z typem całkowitym bez znaku jest konwertowany na typ argumentu z podpisany typ liczby całkowitej.

Dlatego bal < INT32_MINzawsze jest true.

haccks
źródło