Warum wird int i = 1024 * 1024 * 1024 * 1024 fehlerfrei kompiliert?

152

Die Grenze von intliegt zwischen -2147483648 und 2147483647.

Wenn ich eingebe

int i = 2147483648;

dann fordert Eclipse unter "2147483648" eine rote Unterstreichung auf.

Aber wenn ich das mache:

int i = 1024 * 1024 * 1024 * 1024;

es wird gut kompiliert.

public class Test {
    public static void main(String[] args) {        

        int i = 2147483648;                   // error
        int j = 1024 * 1024 * 1024 * 1024;    // no error

    }
}

Vielleicht ist es eine grundlegende Frage in Java, aber ich habe keine Ahnung, warum die zweite Variante keinen Fehler erzeugt.

WUJ
quelle
10
Selbst wenn der Compiler die Berechnung normalerweise als Optimierung in einen einzelnen Wert "kollabieren" würde, wird dies nicht der Fall sein, wenn das Ergebnis ein Überlauf wäre, da keine Optimierung das Verhalten des Programms ändern sollte.
Hot Licks
1
Und es kann nicht interpretiert werden 2147483648: Dieses Wort macht keinen Sinn.
Denys Séguret
1
Und Java meldet keine Ganzzahlüberläufe - die Operation "schlägt fehl".
Hot Licks
5
@JacobKrall: C # meldet dies als Fehler, unabhängig davon, ob die Prüfung aktiviert ist. Alle Berechnungen, die nur aus konstanten Ausdrücken bestehen, werden automatisch überprüft, sofern sie sich nicht in einem nicht aktivierten Bereich befinden.
Eric Lippert
54
Ich rate Ihnen davon ab, "Warum nicht" -Fragen zu StackOverflow zu stellen. Sie sind schwer zu beantworten. Eine "Warum nicht" -Frage setzt voraus, dass die Welt offensichtlich so sein sollte, wie sie nicht ist, und dass es einen guten Grund dafür geben muss. Diese Annahme ist fast nie gültig. Eine genauere Frage wäre etwa "Welcher Abschnitt der Spezifikation beschreibt, wie eine konstante Ganzzahlarithmetik berechnet wird?" oder "Wie werden Ganzzahlüberläufe in Java behandelt?"
Eric Lippert

Antworten:

233

An dieser Aussage ist nichts auszusetzen. Sie multiplizieren nur 4 Zahlen und weisen sie einem Int zu. Es kommt zufällig zu einem Überlauf. Dies unterscheidet sich von der Zuweisung eines einzelnen Literals , das beim Kompilieren auf Grenzen überprüft wird.

Es ist das Out-of-Bound- Literal , das den Fehler verursacht, nicht die Zuweisung :

System.out.println(2147483648);        // error
System.out.println(2147483647 + 1);    // no error

Im Gegensatz dazu würde ein longLiteral gut kompilieren:

System.out.println(2147483648L);       // no error

Beachten Sie, dass in der Tat, das Ergebnis ist immer noch zur Compile-Zeit berechnet , weil 1024 * 1024 * 1024 * 1024a konstanten Ausdruck :

int i = 1024 * 1024 * 1024 * 1024;

wird:

   0: iconst_0      
   1: istore_1      

Beachten Sie, dass das Ergebnis ( 0) einfach geladen und gespeichert wird und keine Multiplikation stattfindet.


Von JLS §3.10.1 (danke an @ChrisK für das Aufrufen in den Kommentaren):

Es ist ein Fehler zur Kompilierungszeit, wenn ein Dezimalliteral vom Typ intgrößer als 2147483648(2 31 ) ist oder wenn das Dezimalliteral an einer2147483648 anderen Stelle als als Operand des unären Minusoperators erscheint ( §15.15.4 ).

arshajii
quelle
12
Und für die Multiplikation sagt das JLS: Wenn eine ganzzahlige Multiplikation überläuft, dann sind das Ergebnis die niederwertigen Bits des mathematischen Produkts, wie sie in einem ausreichend großen Zweierkomplementformat dargestellt werden. Wenn ein Überlauf auftritt, stimmt das Vorzeichen des Ergebnisses möglicherweise nicht mit dem Vorzeichen des mathematischen Produkts der beiden Operandenwerte überein.
Chris K
3
Hervorragende Antwort. Einige Leute scheinen den Eindruck zu haben, dass Überlauf eine Art Fehler oder Misserfolg ist, aber nicht.
Wouter Lievens
3
@ iowatiger08 Die Sprachsemantik wird von der JLS beschrieben, die von der JVM unabhängig ist (es sollte also keine Rolle spielen, welche JVM Sie verwenden).
Arshajii
4
@WouterLievens, Überlauf ist normalerweise eine "ungewöhnliche" Bedingung, wenn nicht eine direkte Fehlerbedingung. Es ist ein Ergebnis der Mathematik mit endlicher Genauigkeit, die die meisten Menschen nicht intuitiv erwarten, wenn sie rechnen. In einigen Fällen -1 + 1ist es harmlos. Aber 1024^4es könnte Menschen mit völlig unerwarteten Ergebnissen blind machen, weit entfernt von dem, was sie erwarten würden. Ich denke, es sollte mindestens eine Warnung oder ein Hinweis für den Benutzer geben und diese nicht stillschweigend ignorieren.
Phil Perry
1
@ iowatiger08: Die Größe von int ist fest; es hängt nicht von der JVM ab. Java ist nicht C.
Martin Schröder
43

1024 * 1024 * 1024 * 1024und 2147483648haben nicht den gleichen Wert in Java.

Tatsächlich ist es in Java 2147483648 nicht einmal ein Wert (obwohl dies der Fall2147483648L ist). Der Compiler weiß buchstäblich nicht, was es ist oder wie man es benutzt. Also jammert es.

1024ist ein gültiges int in Java und ein gültiges intmultipliziert mit einem anderen gültigen intist immer ein gültiges int. Auch wenn es nicht derselbe Wert ist, den Sie intuitiv erwarten würden, da die Berechnung überläuft.

Beispiel

Betrachten Sie das folgende Codebeispiel:

public static void main(String[] args) {
    int a = 1024;
    int b = a * a * a * a;
}

Würden Sie erwarten, dass dies einen Kompilierungsfehler erzeugt? Es wird jetzt etwas rutschiger.
Was ist, wenn wir eine Schleife mit 3 Iterationen setzen und in der Schleife multiplizieren?

Der Compiler kann zwar optimieren, das Verhalten des Programms jedoch nicht ändern.


Einige Informationen darüber, wie dieser Fall tatsächlich behandelt wird:

In Java und vielen anderen Sprachen bestehen Ganzzahlen aus einer festen Anzahl von Bits. Berechnungen, die nicht in die angegebene Anzahl von Bits passen, laufen über . Die Berechnung wird grundsätzlich in Java Modul 2 ^ 32 durchgeführt, wonach der Wert wieder in eine vorzeichenbehaftete Ganzzahl umgewandelt wird.

Andere Sprachen oder APIs verwenden eine dynamische Anzahl von Bits ( BigIntegerin Java), lösen eine Ausnahme aus oder setzen den Wert auf einen magischen Wert, z. B. keine Zahl.

Cruncher
quelle
8
Für mich hat Ihre Aussage " 2147483648IST NOCH KEIN WERT (obwohl dies der Fall 2147483648List)" den Punkt, den @arshajii anstrebte , wirklich zementiert.
Kdbanman
Ah, sorry, ja, das war ich. In Ihrer Antwort fehlte mir der Begriff Überlauf / modulare Arithmetik. Beachten Sie, dass Sie ein Rollback durchführen können, wenn Sie mit meiner Bearbeitung nicht einverstanden sind.
Maarten Bodewes
@owlstead Ihre Bearbeitung ist sachlich korrekt. Mein Grund, es nicht aufzunehmen, war folgender: Unabhängig davon, wie 1024 * 1024 * 1024 * 1024damit umgegangen wird, wollte ich wirklich betonen, dass es nicht dasselbe ist wie Schreiben 2147473648. Es gibt viele Möglichkeiten (und Sie haben einige aufgelistet), wie eine Sprache möglicherweise damit umgehen kann. Es ist einigermaßen getrennt und nützlich. Also werde ich es verlassen. Viele Informationen werden zunehmend notwendig, wenn Sie eine hochrangige Antwort auf eine beliebte Frage haben.
Cruncher
16

Ich habe keine Ahnung, warum die zweite Variante keinen Fehler erzeugt.

Das von Ihnen vorgeschlagene Verhalten, dh die Erstellung einer Diagnosemeldung, wenn eine Berechnung einen Wert erzeugt, der größer als der größte Wert ist, der in einer Ganzzahl gespeichert werden kann, ist eine Funktion . Damit Sie eine Funktion verwenden können, muss die Funktion als gute Idee angesehen, entworfen, spezifiziert, implementiert, getestet, dokumentiert und an Benutzer versendet werden.

Für Java sind eines oder mehrere der Dinge auf dieser Liste nicht aufgetreten, und daher verfügen Sie nicht über die Funktion. Ich weiß nicht welches; Sie müssten einen Java-Designer fragen.

Für C # sind all diese Dinge passiert - vor ungefähr vierzehn Jahren - und so hat das entsprechende Programm in C # seit C # 1.0 einen Fehler erzeugt.

Eric Lippert
quelle
45
Dies fügt nichts hilfreiches hinzu. Obwohl es mir nichts ausmacht, Java zu testen, hat es die Frage des OP überhaupt nicht beantwortet.
Seiyria
29
@Seiyria: Das Originalplakat fragt "Warum nicht?" Frage - "Warum ist die Welt nicht so, wie ich es mir vorstelle?" ist keine genaue technische Frage zum tatsächlichen Code , und dies ist daher eine schlechte Frage für StackOverflow. Die Tatsache, dass die richtige Antwort auf eine vage und nichttechnische Frage vage und nichttechnisch ist, sollte nicht überraschen. Ich ermutige das Originalplakat, eine bessere Frage zu stellen, und vermeide "warum nicht?" Fragen.
Eric Lippert
18
@Seiyria: Die akzeptierte Antwort, die ich notiere, beantwortet auch diese vage und nicht technische Frage nicht. Die Frage ist: "Warum ist das kein Fehler?" und die akzeptierte Antwort lautet "weil es legal ist". Dies wiederholt lediglich die Frage ; Antwort "Warum ist der Himmel nicht grün?" mit "weil es blau ist" beantwortet die Frage nicht. Aber da die Frage eine schlechte Frage ist, beschuldige ich den Antwortenden überhaupt nicht; Die Antwort ist eine völlig vernünftige Antwort auf eine schlechte Frage.
Eric Lippert
13
Herr Eric, dies ist die Frage, die ich gestellt habe: "Warum int i = 1024 * 1024 * 1024 * 1024; ohne Fehlerbericht in Eclipse?". und die Antwort von Arshajii ist genau das, was ich was (vielleicht mehr). Manchmal kann ich keine Fragen sehr genau ausdrücken. Ich denke, deshalb gibt es einige Leute, die einige gestellte Fragen in Stackoverflow genauer ändern. Ich denke, wenn ich die Antwort "weil es legal ist" erhalten möchte, werde ich diese Frage nicht posten. Ich werde mein Bestes geben, um einige "regelmäßige Fragen" zu stellen, aber bitte verstehe jemanden wie mich, der Student ist und nicht so professionell. Vielen Dank.
WUJ
5
@WUJ Diese Antwort bietet meiner Meinung nach zusätzliche Einblicke und Perspektiven. Nachdem ich alle Antworten gelesen hatte, stellte ich fest, dass diese Antwort genauso gültig ist wie die anderen Antworten. Außerdem wird das Bewusstsein dafür geschärft, dass Entwickler nicht die einzigen Implementierer einiger Softwareprodukte sind.
SoftwareCarpenter
12

Neben der Antwort von arshajii möchte ich noch eines zeigen:

Es ist nicht die Zuweisung , die den Fehler verursacht, sondern einfach die Verwendung des Literal . Wenn du es versuchst

long i = 2147483648;

Sie werden feststellen, dass es auch einen Kompilierungsfehler verursacht, da die rechte Seite immer noch eine ist int -literal ist und außerhalb des Bereichs liegt.

Operationen mit int-Werten (und dazu gehören auch Zuweisungen) können ohne Kompilierungsfehler (und auch ohne Laufzeitfehler) überlaufen, aber der Compiler kann diese zu großen Literale einfach nicht verarbeiten.

piet.t.
quelle
1
Richtig. Das Zuweisen eines Int zu einem Long enthält eine implizite Umwandlung. Aber der Wert kann niemals als int existieren, um überhaupt gewirkt zu werden :)
Cruncher
4

A: Weil es kein Fehler ist.

Hintergrund: Die Multiplikation 1024 * 1024 * 1024 * 1024führt zu einem Überlauf. Ein Überlauf ist sehr oft ein Fehler. Unterschiedliche Programmiersprachen führen bei Überläufen zu unterschiedlichem Verhalten. Zum Beispiel nennen C und C ++ es "undefiniertes Verhalten" für vorzeichenbehaftete Ganzzahlen, und das Verhalten ist als vorzeichenlose Ganzzahlen definiert (nehmen Sie das mathematische Ergebnis, addieren Sie UINT_MAX + 1, solange das Ergebnis negativ ist, subtrahieren Sie UINT_MAX + 1, solange das Ergebnis größer als istUINT_MAX ).

Im Fall von Java, wenn das Ergebnis einer Operation mit int Werten nicht im zulässigen Bereich liegt, addiert oder subtrahiert Java konzeptionell 2 ^ 32, bis das Ergebnis im zulässigen Bereich liegt. Die Aussage ist also völlig legal und nicht fehlerhaft. Es führt einfach nicht zu dem erhofften Ergebnis.

Sie können sicherlich darüber streiten, ob dieses Verhalten hilfreich ist und ob der Compiler Sie warnen sollte. Ich würde persönlich sagen, dass eine Warnung sehr nützlich wäre, aber ein Fehler wäre falsch, da es sich um legales Java handelt.

gnasher729
quelle