Warum ist 0 <-0x80000000?

253

Ich habe unten ein einfaches Programm:

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

Die Bedingung if(bal < INT32_MIN )ist immer wahr. Wie ist es möglich?

Es funktioniert gut, wenn ich das Makro in ändere:

#define INT32_MIN        (-2147483648L)

Kann jemand auf das Problem hinweisen?

Jayesh Bhoi
quelle
3
Wie viel ist CHAR_BIT * sizeof(int)?
5gon12eder
1
Haben Sie versucht, bal auszudrucken?
Ryan Fitzpatrick
10
IMHO desto interessanter ist , dass es wahr ist , nur für -0x80000000, aber falsch für -0x80000000L, -2147483648und -2147483648L(gcc 4.1.2), so ist die Frage: warum ist der int wörtlichen -0x80000000unterscheidet sich von der int wörtliche -2147483648?
Andreas Fester
2
@Bathsheba Ich habe gerade ein Programm auf dem Online-Compiler tutorialspoint.com/codingground.htm
Jayesh Bhoi
2
Wenn Sie jemals bemerkt haben, dass (einige Inkarnationen von) <limits.h>definiert INT_MINals (-2147483647 - 1), wissen Sie jetzt warum.
zwol

Antworten:

363

Das ist ziemlich subtil.

Jedes ganzzahlige Literal in Ihrem Programm hat einen Typ. Welchen Typ es hat, wird durch eine Tabelle in 6.4.4.1 geregelt:

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

Wenn eine Literalzahl nicht in den Standardtyp passt int, wird der nächstgrößere Typ wie in der obigen Tabelle angegeben versucht. Für reguläre Dezimalzahl-Literale lautet es also wie folgt:

  • Versuchen int
  • Wenn es nicht passt, versuchen Sie es long
  • Wenn es nicht passt, versuchen Sie es long long.

Hex-Literale verhalten sich jedoch anders! Wenn das Literal nicht in einen signierten Typ wie passt int, wird es zuerst versucht, unsigned intbevor größere Typen ausprobiert werden. Siehe den Unterschied in der obigen Tabelle.

Auf einem 32-Bit-System ist Ihr Literal 0x80000000also vom Typ unsigned int.

Dies bedeutet, dass Sie den unären -Operator auf das Literal anwenden können, ohne ein implementierungsdefiniertes Verhalten aufzurufen, wie Sie es sonst tun würden, wenn eine vorzeichenbehaftete Ganzzahl überläuft. Stattdessen erhalten Sie den Wert 0x80000000, einen positiven Wert.

bal < INT32_MINruft die üblichen arithmetischen Konvertierungen auf und das Ergebnis des Ausdrucks 0x80000000wird von unsigned intbis heraufgestuft long long. Der Wert 0x80000000bleibt erhalten und 0 ist kleiner als 0x80000000, daher das Ergebnis.

Wenn Sie das Literal durch das ersetzen, verwenden 2147483648LSie die Dezimalschreibweise. Daher wählt der Compiler nicht aus unsigned int, sondern versucht, es in a einzufügen long. Auch das Suffix L sagt, dass Sie ein long wenn möglich wollen . Das L-Suffix hat tatsächlich ähnliche Regeln, wenn Sie die in 6.4.4.1 erwähnte Tabelle weiter lesen: Wenn die Zahl nicht in die angeforderte passt long, was im 32-Bit-Fall nicht der Fall ist, gibt Ihnen der Compiler einen Ort, an long longdem sie steht wird gut passen.

Lundin
quelle
3
"... Ersetzen Sie das Literal durch -2147483648L. Sie erhalten explizit ein Long, das signiert ist." Hmmm, in einem 32-Bit- longSystem 2147483648Lwird nicht in ein passen long, so wird es long long, dann wird das -angewendet - oder so dachte ich.
chux
2
@ASH Weil die maximale Anzahl, die ein Int haben kann, dann ist 0x7FFFFFFF. Probieren Sie es aus:#include <limits.h> printf("%X\n", INT_MAX);
Lundin
5
@ASH Verwechseln Sie die hexadezimale Darstellung von Ganzzahlliteralen im Quellcode nicht mit der zugrunde liegenden binären Darstellung einer vorzeichenbehafteten Zahl. Das 0x7FFFFFFFim Quellcode geschriebene Literal ist immer eine positive Zahl, aber Ihre intVariable kann natürlich rohe Binärzahlen bis zum Wert 0xFFFFFFFF enthalten.
Lundin
2
@ASH ìnt n = 0x80000000erzwingt eine Konvertierung vom vorzeichenlosen Literal in einen vorzeichenbehafteten Typ. Was passieren wird, liegt bei Ihrem Compiler - es ist ein implementierungsdefiniertes Verhalten. In diesem Fall wurde das gesamte Literal in intdas Vorzeichenbit eingefügt. Auf anderen Systemen ist es möglicherweise nicht möglich, den Typ darzustellen, und Sie rufen undefiniertes Verhalten auf - das Programm kann abstürzen. Wenn Sie dies tun int n=2147483648;, erhalten Sie das gleiche Verhalten, das überhaupt nicht mit der Hex-Notation zusammenhängt.
Lundin
3
Die Erklärung, wie unär -auf vorzeichenlose Ganzzahlen angewendet wird, könnte etwas erweitert werden. Ich hatte immer angenommen (obwohl ich mich glücklicherweise nie auf die Annahme verlassen hatte), dass vorzeichenlose Werte zu vorzeichenbehafteten Werten "heraufgestuft" würden oder dass das Ergebnis möglicherweise undefiniert wäre. (Ehrlich gesagt sollte es ein Kompilierungsfehler sein; was bedeutet das - 3uüberhaupt?)
Kyle Strand
27

0x80000000ist ein unsignedLiteral mit dem Wert 2147483648.

Die Anwendung des unären Minus auf diese noch gibt Ihnen einen unsigned - Typ mit einem Wert ungleich Null. (Für einen Wert ungleich Null ist der Wert x, den Sie am Ende erhalten UINT_MAX - x + 1.)

Bathseba
quelle
23

Dieses ganzzahlige Literal 0x80000000hat den Typ unsigned int.

Nach dem C-Standard (6.4.4.1 Integer-Konstanten)

5 Der Typ einer Ganzzahlkonstante ist der erste der entsprechenden Liste, in der ihr Wert dargestellt werden kann.

Und diese ganzzahlige Konstante kann durch den Typ von dargestellt werden unsigned int.

Also dieser Ausdruck

-0x80000000hat den gleichen unsigned intTyp. Darüber hinaus hat es den gleichen Wert 0x80000000in der Komplementdarstellung der beiden, der die folgende Methode berechnet

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

Dies hat einen Nebeneffekt, wenn zum Beispiel geschrieben werden soll

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

Das Ergebnis wird wieder sein INT_MIN.

Also in diesem Zustand

bal < INT32_MIN

Es wird 0mit einem vorzeichenlosen Wert verglichen 0x80000000, der gemäß den Regeln der üblichen arithmetischen Konvertierungen in den Typ long long int konvertiert wurde.

Es ist offensichtlich, dass 0 kleiner als ist 0x80000000.

Vlad aus Moskau
quelle
12

Die numerische Konstante 0x80000000ist vom Typ unsigned int. Wenn wir -0x800000002s Komplimente machen und Mathe machen, bekommen wir folgendes:

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

Also -0x80000000 == 0x80000000. Und Vergleichen (0 < 0x80000000)(da 0x80000000nicht signiert ist) ist wahr.

dbush
quelle
Dies setzt 32-Bit- ints voraus . Obwohl dies eine sehr häufige Wahl ist, kann jede Implementierung intentweder enger oder breiter sein. Es ist jedoch eine korrekte Analyse für diesen Fall.
John Bollinger
Dies ist für den OP-Code nicht relevant, -0x80000000ist eine vorzeichenlose Arithmetik. ~0x800000000ist anderer Code.
MM
Dies scheint einfach die beste und richtige Antwort für mich zu sein. @MM er erklärt, wie man ein Zweierkomplement nimmt. Diese Antwort befasst sich speziell damit, was das negative Vorzeichen mit der Zahl macht.
Octopus
@Octopus das negative Vorzeichen wendet kein 2er-Komplement auf die Zahl (!) An. Obwohl dies klar erscheint, beschreibt es nicht, was im Code passiert -0x80000000! Tatsächlich ist die Ergänzung von 2 für diese Frage völlig irrelevant.
MM
12

Ein Punkt der Verwirrung tritt auf, wenn man denkt, dass dies -Teil der numerischen Konstante ist.

Im folgenden Code 0x80000000ist die numerische Konstante. Sein Typ ist nur darauf bestimmt. Das -wird danach angewendet und ändert den Typ nicht .

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

Rohe schmucklose numerische Konstanten sind positiv.

Wenn es dezimal ist, dann zugewiesen ist der Typ erste Art , die es halten wird: int, long, long long.

Wenn die Konstante Oktal oder hexadezimal ist, wird es die erste Art , die es gilt: int, unsigned, long, unsigned long,long long , unsigned long long.

0x80000000, auf dem OP-System erhält den Typ von unsignedoderunsigned long . In beiden Fällen handelt es sich um einen vorzeichenlosen Typ.

-0x80000000ist auch ein Wert ungleich Null und ein Typ ohne Vorzeichen, der größer als 0 ist. Wenn Code dies mit a vergleicht long long, werden die Werte auf den beiden Seiten des Vergleichs nicht geändert, 0 < INT32_MINwas wahr ist.


Eine alternative Definition vermeidet dieses merkwürdige Verhalten

#define INT32_MIN        (-2147483647 - 1)

Lass uns eine Weile im Fantasieland spazieren gehen, wo int und unsignedsind 48-Bit.

Dann 0x80000000passt hinein intund so ist der Typint . -0x80000000ist dann eine negative Zahl und das Ergebnis des Ausdrucks ist unterschiedlich.

[Zurück zum richtigen Wort]

Da es 0x80000000in einen vorzeichenlosen Typ vor einem vorzeichenbehafteten Typ passt, da dieser nur größer als some_signed_MAXnoch some_unsigned_MAXdarin ist, handelt es sich um einen vorzeichenlosen Typ.

chux - Monica wieder einsetzen
quelle
8

C hat die Regel, dass das ganzzahlige Literal sein kann signedoder unsigneddavon abhängt, ob es in signedoder passt unsigned(ganzzahlige Heraufstufung). Auf einer 32Bit-Maschine wird das Literal 0x80000000sein unsigned. Das Komplement von 2 -0x80000000befindet sich 0x80000000 auf einem 32-Bit-Computer. Daher erfolgt der Vergleich bal < INT32_MINzwischen signedund unsignedund vor dem Vergleich gemäß der C-Regel unsigned intwird in konvertiert long long.

C11: 6.3.1.8/1:

[...] Wenn andernfalls der Typ des Operanden mit vorzeichenbehaftetem Integer-Typ alle Werte des Typs des Operanden mit vorzeichenlosem Integer-Typ darstellen kann, wird der Operand mit vorzeichenlosem Integer-Typ in den Typ des Operanden mit konvertiert Ganzzahliger Typ mit Vorzeichen.

Deshalb bal < INT32_MINist immer true.

Haccks
quelle