Warum geht dies in eine Endlosschleife?


493

Ich habe folgenden Code:

public class Tests {
    public static void main(String[] args) throws Exception {
        int x = 0;
        while(x<3) {
            x = x++;
            System.out.println(x);
        }
    }
}

Wir wissen, dass er nur x++oder hätte schreiben sollen x=x+1, aber x = x++darauf sollte er sich zuerst selbst zuschreiben xund später erhöhen. Warum geht es xweiter 0als Wert?

--aktualisieren

Hier ist der Bytecode:

public class Tests extends java.lang.Object{
public Tests();
  Code:
   0:   aload_0
   1:   invokespecial   #1; //Method java/lang/Object."<init>":()V
   4:   return

public static void main(java.lang.String[])   throws java.lang.Exception;
  Code:
   0:   iconst_0
   1:   istore_1
   2:   iload_1
   3:   iconst_3
   4:   if_icmpge   22
   7:   iload_1
   8:   iinc    1, 1
   11:  istore_1
   12:  getstatic   #2; //Field java/lang/System.out:Ljava/io/PrintStream;
   15:  iload_1
   16:  invokevirtual   #3; //Method java/io/PrintStream.println:(I)V
   19:  goto    2
   22:  return

}

Ich werde über die Anweisungen lesen, um zu versuchen zu verstehen ...


8
Ich vermute, was passiert ist: 1. Laden Sie x in ein Register (= 0); 2. Inkrementiere x (x = 1); 3. Speichern Sie den Registerwert in x (x = 0). In C / C ++ wäre dies ein undefiniertes Verhalten, da es keinen formalen Sequenzpunkt gibt, um die Reihenfolge von 2 und 3 zu definieren. Hoffentlich kann Ihnen jemand etwas Äquivalentes aus der Java-Spezifikation zitieren.
Rup

19
Wir haben dies in C ++ versucht, um zu sehen, was passieren würde, und es gibt 1,2,3 aus und wird beendet. Ich habe nicht damit gerechnet. Ich gehe davon aus, dass es vom Compiler abhängig ist, da es sich um ein undefiniertes Verhalten handelt. Wir haben das gnu g ++ verwendet.
Trauer

13
@saj x++ist nach dem Inkrementieren; x=ist die Zuordnung des Ergebnisses ; Das Ergebnis von x++ist das Original x(und es gibt einen Nebeneffekt des Inkrements, aber das ändert nichts am Ergebnis). Dies kann also interpretiert werden alsvar tmp = x; x++; x = tmp;
Marc Gravell

5
Jetzt, wo ich eine beliebte Frage habe, bedauere ich, dass die (wiederholten) Antworten nicht aufhören zu kommen, selbst nachdem die richtige Antwort ausgewählt wurde. Mein "Letzte Aktivität" Bildschirm ist voll von den gleichen Antworten und kommt mehr ...
Tom Brito

3
@Rob Vermeulen Vielleicht möchten Sie die vollständige Frage lesen, bevor Sie einen Kommentar abgeben ..;) Dies war ein Code, der von einem meiner Schüler erstellt wurde, und ich war neugierig, warum dies so ist.
Tom Brito

Antworten:


354

Hinweis : Ursprünglich habe ich in dieser Antwort zur Veranschaulichung C # -Code angegeben, da Sie mit C # intParameter als Referenz mit dem refSchlüsselwort übergeben können. Ich habe beschlossen, es mit tatsächlichem legalem Java-Code zu aktualisieren, indem MutableIntich die erste Klasse verwende, die ich bei Google gefunden habe, um ungefähr zu wissen, was refin C # funktioniert. Ich kann nicht wirklich sagen, ob das hilft oder die Antwort verletzt. Ich werde sagen, dass ich persönlich nicht so viel Java-Entwicklung gemacht habe; Soweit ich weiß, könnte es viel idiomatischere Möglichkeiten geben, diesen Punkt zu veranschaulichen.


Wenn wir eine Methode aufschreiben, die das Äquivalent zu dem x++tut, was sie tut, wird dies möglicherweise klarer.

public MutableInt postIncrement(MutableInt x) {
    int valueBeforeIncrement = x.intValue();
    x.add(1);
    return new MutableInt(valueBeforeIncrement);
}

Recht? Inkrementieren Sie den übergebenen Wert und geben Sie den ursprünglichen Wert zurück: Dies ist die Definition des Nachinkrementierungsoperators.

Lassen Sie uns nun sehen, wie sich dieses Verhalten in Ihrem Beispielcode auswirkt:

MutableInt x = new MutableInt();
x = postIncrement(x);

postIncrement(x)macht was? Inkremente x, ja. Und dann gibt , was x war vor dem Zuwachs . Dieser Rückgabewert wird dann zugewiesen x.

Die Reihenfolge der zugewiesenen Werte xist also 0, dann 1, dann 0.

Dies könnte noch deutlicher werden, wenn wir das Obige neu schreiben:

MutableInt x = new MutableInt();    // x is 0.
MutableInt temp = postIncrement(x); // Now x is 1, and temp is 0.
x = temp;                           // Now x is 0 again.

Ihre Fixierung auf die Tatsache, dass, wenn Sie xauf der linken Seite der obigen Zuordnung durch y"Sie können sehen, dass es zuerst x erhöht und später y zuschreibt", mich verwirrt erscheinen. Es ist nicht xdas, was zugewiesen wird y; Dies ist der Wert, der zuvor zugewiesen wurdex . Das Injizieren unterscheidet ydie Dinge nicht von dem obigen Szenario. Wir haben einfach:

MutableInt x = new MutableInt();    // x is 0.
MutableInt y = new MutableInt();    // y is 0.
MutableInt temp = postIncrement(x); // Now x is 1, and temp is 0.
y = temp;                           // y is still 0.

Es ist also klar: x = x++Ändert den Wert von x effektiv nicht. Es bewirkt immer, dass x die Werte x 0 , dann x 0 + 1 und dann wieder x 0 hat.


Update : Damit Sie nicht bezweifeln, dass xzwischen dem Inkrementierungsvorgang und der Zuweisung im obigen Beispiel jemals 1 "zugewiesen" wird, habe ich eine kurze Demo zusammengestellt, um zu veranschaulichen, dass dieser Zwischenwert tatsächlich "existiert", obwohl dies der Fall ist Niemals auf dem ausführenden Thread "gesehen" werden.

Die Demo ruft x = x++;in einer Schleife auf, während ein separater Thread den Wert von kontinuierlich xan die Konsole druckt .

public class Main {
    public static volatile int x = 0;

    public static void main(String[] args) {
        LoopingThread t = new LoopingThread();
        System.out.println("Starting background thread...");
        t.start();

        while (true) {
            x = x++;
        }
    }
}

class LoopingThread extends Thread {
    public @Override void run() {
        while (true) {
            System.out.println(Main.x);
        }
    }
}

Unten finden Sie einen Auszug aus der Ausgabe des obigen Programms. Beachten Sie das unregelmäßige Auftreten von Einsen und Nullen.

Hintergrund-Thread starten ...
0
0
1
1
0
0
0
0
0
0
0
0
0
0
1
0
1

1
Sie müssen keine Klasse erstellen, um in Java als Referenz zu übergeben (obwohl dies sicherlich funktionieren würde). Sie können die IntegerKlasse verwenden, die Teil der Standardbibliothek ist, und sie hat sogar den Vorteil, dass sie int fast transparent automatisch von und zu verpackt wird .
Rmeador

3
@rmeador Integer ist unveränderlich, sodass Sie den Wert immer noch nicht ändern können. AtomicInteger ist jedoch veränderlich.
ILMTitan

5
@Dan: Übrigens muss xin Ihrem letzten Beispiel deklariert werden volatile, sonst ist es ein undefiniertes Verhalten und das Sehen von 1s ist implementierungsspezifisch.
Axtavt

4
@burkestar: Ich glaube nicht , dass Link ist ganz in diesem Fall angemessen, da es sich um eine Java - Frage ist und (wenn ich nicht irre) wird das Verhalten tatsächlich nicht definiert in C ++.
Dan Tao

5
@ Tom Brito - in C ist es nicht definiert ... das ++ könnte vor oder nach der Zuweisung erfolgen. In der Praxis gibt es möglicherweise einen Compiler, der das Gleiche wie Java tut, aber Sie möchten nicht darauf wetten.
Detly

170

x = x++ funktioniert folgendermaßen:

  • Zuerst wird der Ausdruck ausgewertet x++. Die Auswertung dieses Ausdrucks erzeugt einen Ausdruckswert (der der Wert xvor dem Inkrement ist) und Inkremente x.
  • Später weist es den Ausdruckswert zu xund überschreibt den inkrementierten Wert.

Die Reihenfolge der Ereignisse sieht also wie folgt aus (es handelt sich um einen tatsächlich dekompilierten Bytecode, wie er von javap -cmit meinen Kommentaren erstellt wurde):

   8: iload_1 // Den aktuellen Wert von x im Stapel speichern
   9: iinc 1, 1 // Inkrementiere x (ändert den Stapel nicht)
   12: istore_1 // Schreibe einen Erinnerungswert vom Stapel nach x

Zum Vergleich x = ++x:

   8: iinc 1, 1 // Inkrementiere x
   11: iload_1 // Wert von x auf Stapel stapeln
   12: istore_1 // Pop-Wert vom Stapel auf x

Wenn Sie einen Test durchführen, können Sie sehen, dass er zuerst inkrementiert und später Attribute. Es sollte also nicht Null zuordnen.
Tom Brito

2
@ Tom, das ist jedoch der Punkt - da dies alles eine einzelne Sequenz ist, werden die Dinge in einer nicht offensichtlichen (und wahrscheinlich undefinierten) Reihenfolge ausgeführt. Wenn Sie versuchen, dies zu testen, fügen Sie einen Sequenzpunkt hinzu und erhalten ein anderes Verhalten.
Rup

In Bezug auf Ihre Bytecode-Ausgabe: Beachten Sie, dass iinceine Variable inkrementiert wird, kein Stapelwert inkrementiert wird und kein Wert auf dem Stapel verbleibt (im Gegensatz zu fast jeder anderen arithmetischen Operation). Möglicherweise möchten Sie den von generierten Code ++xzum Vergleich hinzufügen .
Anon

3
@Rep Es ist möglicherweise nicht in C oder C ++ definiert, aber in Java ist es gut definiert.
ILMTitan


104

Dies geschieht, weil der Wert von xüberhaupt nicht erhöht wird.

x = x++;

ist äquivalent zu

int temp = x;
x++;
x = temp;

Erläuterung:

Schauen wir uns den Bytecode für diese Operation an. Betrachten Sie eine Beispielklasse:

class test {
    public static void main(String[] args) {
        int i=0;
        i=i++;
    }
}

Wenn wir nun den Klassen-Disassembler ausführen, erhalten wir:

$ javap -c test
Compiled from "test.java"
class test extends java.lang.Object{
test();
  Code:
   0:    aload_0
   1:    invokespecial    #1; //Method java/lang/Object."<init>":()V
   4:    return

public static void main(java.lang.String[]);
  Code:
   0:    iconst_0
   1:    istore_1
   2:    iload_1
   3:    iinc    1, 1
   6:    istore_1
   7:    return
}

Jetzt ist die Java-VM stapelbasiert, was bedeutet, dass für jede Operation die Daten auf den Stapel übertragen werden und aus dem Stapel die Daten herausspringen, um die Operation auszuführen. Es gibt auch eine andere Datenstruktur, normalerweise ein Array zum Speichern der lokalen Variablen. Die lokalen Variablen erhalten IDs, die nur die Indizes für das Array sind.

Schauen wir uns die Mnemonik in main()Methode an:

  • iconst_0: Der konstante Wert 0 wird auf den Stapel übertragen.
  • istore_1: Das oberste Element des Stapels ausgeklappt ist und in dem lokalen Variable mit Index gespeichert ist, 1
    der ist x.
  • iload_1: Der Wert an der Stelle 1, deren Wert x ist 0, wird in den Stapel verschoben.
  • iinc 1, 1: Der Wert am Speicherort 1wird um erhöht 1. So wird xjetzt 1.
  • istore_1: Der Wert oben im Stapel wird im Speicherort gespeichert 1. Das ist 0dem x Überschreiben seines inkrementierten Wertes zugeordnet.

Daher xändert sich der Wert von nicht, was zur Endlosschleife führt.


5
Eigentlich wird es inkrementiert (das ist die Bedeutung von ++), aber die Variable wird später überschrieben.
Progman

10
int temp = x; x = x + 1; x = temp;Es ist besser, in Ihrem Beispiel keine Tautologie zu verwenden.
Scott Chamberlain

52
  1. Die Präfixnotation erhöht die Variable, BEVOR der Ausdruck ausgewertet wird.
  2. Die Postfix-Notation wird NACH der Ausdrucksauswertung erhöht.

" =" Hat jedoch eine niedrigere Operatorrangfolge als " ++".

Also x=x++;sollte wie folgt bewertet werden

  1. x für den Einsatz vorbereitet (ausgewertet)
  2. x erhöht
  3. Vorheriger Wert von xzugewiesen an x.

Dies ist die beste Antwort. Einige Markups hätten dazu beigetragen, dass es ein bisschen mehr auffiel.
Justin Force

1
Das ist falsch. Es geht nicht um Vorrang. ++hat eine höhere Priorität als =in C und C ++, aber die Anweisung ist in diesen Sprachen undefiniert.
Matthew Flaschen

2
Die ursprüngliche Frage ist über Java
Jaydee

34

Keine der Antworten war genau richtig, also geht es weiter:

Wenn Sie schreiben int x = x++, weisen Sie nicht xzu, dass es sich um den neuen Wert handelt, xsondern um den Rückgabewert des x++Ausdrucks. Was zufällig der ursprüngliche Wert von ist x, wie in Colin Cochranes Antwort angedeutet .

Testen Sie zum Spaß den folgenden Code:

public class Autoincrement {
        public static void main(String[] args) {
                int x = 0;
                System.out.println(x++);
                System.out.println(x);
        }
}

Das Ergebnis wird sein

0
1

Der Rückgabewert des Ausdrucks ist der Anfangswert von x, der Null ist. Aber später, wenn xwir den Wert von lesen , erhalten wir den aktualisierten Wert, das heißt einen.


Ich werde versuchen, die Bytecode-Zeilen zu verstehen, siehe mein Update, damit es klar wird .. :)
Tom Brito

Die Verwendung von println () war für mich sehr hilfreich, um dies zu verstehen.
ErikE

29

Es wurde bereits von anderen gut erklärt. Ich füge nur die Links zu den relevanten Java-Spezifikationsabschnitten hinzu.

x = x ++ ist ein Ausdruck. Java folgt der Auswertungsreihenfolge . Zuerst wird der Ausdruck x ++ ausgewertet, der x erhöht und den Ergebniswert auf den vorherigen Wert von x setzt . Dann wird das Ausdrucksergebnis der Variablen x zugewiesen . Am Ende ist x wieder auf dem vorherigen Wert.


1
+1. Dies ist bei weitem die beste Antwort auf die eigentliche Frage: "Warum?"
Matthew Flaschen

18

Diese Aussage:

x = x++;

bewertet wie folgt:

  1. Schieben xauf den Stapel;
  2. Inkrement x;
  3. Pop xvom Stapel.

Der Wert bleibt also unverändert. Vergleichen Sie das mit:

x = ++x;

welches bewertet als:

  1. Inkrement x;
  2. Schieben xauf den Stapel;
  3. Pop xvom Stapel.

Was Sie wollen ist:

while (x < 3) {
  x++;
  System.out.println(x);
}

13
Auf jeden Fall die richtige Implementierung, aber die Frage ist 'warum?'.
p.campbell

1
Der ursprüngliche Code verwendete Post-Inkrement für x und wies es dann x zu. x wird vor dem Inkrementieren an x ​​gebunden, daher werden die Werte niemals geändert.
wkl

5
@cletus Ich bin nicht der Downvoter, aber Ihre erste Antwort enthielt keine Erklärung. Es hieß nur do 'x ++'.
Petar Minchev

4
@cletus: Ich habe nicht abgelehnt, aber Ihre Antwort war ursprünglich nur das x++Code-Snippet.
p.campbell

10
Die Erklärung ist auch falsch. Wenn der Code zuerst x x zuweist und dann x erhöht, funktioniert dies einwandfrei. Ändern x++;Sie einfach Ihre Lösung in x=x; x++;und Sie tun, was Sie behaupten, dass der ursprüngliche Code tut.
Wooble

10

Die Antwort ist ziemlich einfach. Es hat mit der Reihenfolge zu tun, in der die Dinge ausgewertet werden. x++gibt den Wert zurück xund erhöht sich dann x.

Folglich ist der Wert des Ausdrucks x++ist 0. Sie weisen also x=0jedes Mal in der Schleife zu. Erhöht x++diesen Wert zwar, aber das passiert vor der Zuweisung.


1
Wow, es gibt so viele Details auf dieser Seite, wenn die Antwort kurz und einfach ist, dh diese.
Charles Goodwin

8

Von http://download.oracle.com/javase/tutorial/java/nutsandbolts/op1.html

Die Inkrementierungs- / Dekrementierungsoperatoren können vor (Präfix) oder nach (Postfix) dem Operanden angewendet werden. Das Codeergebnis ++; und ++ Ergebnis; Beides endet damit, dass das Ergebnis um eins erhöht wird. Der einzige Unterschied besteht darin, dass die Präfixversion (++ Ergebnis) den inkrementierten Wert ergibt, während die Postfixversion (Ergebnis ++) den ursprünglichen Wert ergibt . Wenn Sie nur ein einfaches Inkrementieren / Dekrementieren durchführen, spielt es keine Rolle, welche Version Sie wählen. Wenn Sie diesen Operator jedoch teilweise in einem größeren Ausdruck verwenden, kann der von Ihnen ausgewählte Operator einen signifikanten Unterschied bewirken.

Versuchen Sie zur Veranschaulichung Folgendes:

    int x = 0;
    int y = 0;
    y = x++;
    System.out.println(x);
    System.out.println(y);

Welches wird 1 und 0 drucken.


1
Es geht jedoch nicht um das Bewertungsergebnis, sondern um die Reihenfolge der Geschäfte.
Rup

2
Ich stimme dir nicht zu. Wenn x = 0, dann gibt x ++ 0 zurück. Daher führt x = x ++ zu x = 0.
Colin Cochrane

Rup hat recht damit. In diesem speziellen Fall geht es um die Reihenfolge der Geschäfte. y = x ++ ist nicht dasselbe wie x = x ++; Bei letzterem werden x 2 Werte im selben Ausdruck zugewiesen. Der linken Hand x wird das Ergebnis der Auswertung des Ausdrucks x ++ zugewiesen , der 0 ist. Die rechte Seite x wird auf 1 erhöht. In welcher Reihenfolge diese 2 Zuweisungen erfolgen, ist das Problem. Aus früheren Beiträgen geht hervor, dass dies wie folgt funktioniert: eval = x ++ => eval == 0: Inkrementiere rechts x => x == 1: links x = eval => x == 0
Michael Ekoka

7

Sie erhalten effektiv das folgende Verhalten.

  1. Nimm den Wert von x (der 0 ist) als "das Ergebnis" der rechten Seite
  2. Erhöhen Sie den Wert von x (also ist x jetzt 1)
  3. Weisen Sie x das Ergebnis der rechten Seite (die als 0 gespeichert wurde) zu (x ist jetzt 0).

Die Idee ist, dass der Post-Inkrement-Operator (x ++) diese fragliche Variable inkrementiert, nachdem sie ihren Wert zur Verwendung in der Gleichung zurückgegeben hat, in der sie verwendet wird.

Bearbeiten: Wegen des Kommentars etwas hinzufügen. Betrachten Sie es wie folgt.

x = 1;        // x == 1
x = x++ * 5;
              // First, the right hand side of the equation is evaluated.
  ==>  x = 1 * 5;    
              // x == 2 at this point, as it "gave" the equation its value of 1
              // and then gets incremented by 1 to 2.
  ==>  x = 5;
              // And then that RightHandSide value is assigned to 
              // the LeftHandSide variable, leaving x with the value of 5.

OK, aber was gibt die Reihenfolge der Schritte 2 und 3 an?
Rup

@ Rup - Die Sprache definiert es. Die rechte Seite der Gleichung wird zuerst ausgewertet (in diesem Fall "x ++"), und das Ergebnis wird der Variablen auf der linken Seite zugewiesen. So funktioniert die Sprache. Soweit das "x ++" "x" für die Gleichung "zurückgibt", funktioniert der Postfix-Inkrementierungsoperator so (gibt den Wert von x zurück und erhöht ihn dann). Wenn es "--x" gewesen wäre, wäre es gewesen (inkrementiere x, dann gib den Wert zurück). Rückkehr ist dort nicht das richtige Wort, aber Sie haben die Idee.
RHSeeger

7

Sie brauchen den Maschinencode nicht wirklich, um zu verstehen, was passiert.

Nach den Definitionen:

  1. Der Zuweisungsoperator wertet den Ausdruck auf der rechten Seite aus und speichert ihn in einer temporären Variablen.

    1.1. Der aktuelle Wert von x wird in diese temporäre Variable kopiert

    1.2. x wird jetzt erhöht.

  2. Die temporäre Variable wird dann in die linke Seite des Ausdrucks kopiert, die zufällig x ist! Deshalb wird der alte Wert von x wieder in sich selbst kopiert.

Es ist ziemlich einfach.


5

Dies liegt daran, dass es in diesem Fall nie erhöht wird. x++verwendet zuerst den Wert, bevor er wie in diesem Fall inkrementiert wird:

x = 0;

Aber wenn Sie dies tun, wird ++x;dies zunehmen.


Wenn Sie einen Test durchführen, können Sie sehen, dass er zuerst inkrementiert und später Attribute. Es sollte also nicht Null zuordnen.
Tom Brito

1
@ Tom: siehe meine Antwort - ich zeige in einem Test, dass x ++ tatsächlich den alten Wert von x zurückgibt. Dort bricht es.
Robert Munteanu

"Wenn Sie einen Test machen" - einige Leute scheinen zu glauben, dass ein in C geschriebener Test uns sagt, was Java tun wird, wenn er uns nicht einmal sagt, was C tun wird.
Jim Balter

3

Der Wert bleibt bei 0, da der Wert 0 x++ist. In diesem Fall spielt es keine Rolle, ob der Wert von xerhöht wird oder nicht, die Zuweisung x=0wird ausgeführt. Dadurch wird der temporär inkrementierte Wert von x(der für "sehr kurze Zeit" 1 war) überschrieben .


Aber x ++ ist eine Nachoperation. X müsste also nach Abschluss der Zuweisung inkrementiert werden.
Sagar V

1
@Sagar V: nur für den Ausdruck x++, nicht für die gesamte Aufgabex=x++;
Progman

Nein, ich denke, es muss nur erhöht werden, nachdem der Wert von x, der in der Zuweisung verwendet werden soll, gelesen wurde.
Rup

1

Dies funktioniert so, wie Sie es von dem anderen erwarten. Es ist der Unterschied zwischen Präfix und Postfix.

int x = 0; 
while (x < 3)    x = (++x);

1

Stellen Sie sich x ++ als einen Funktionsaufruf vor, der zurückgibt, was X vor dem Inkrement war (deshalb wird es als Nachinkrement bezeichnet).

Die Operationsreihenfolge lautet also:
1: Zwischenspeichern des Werts von x vor dem Inkrementieren
2: Inkrementieren von x
3: Zurückgeben des zwischengespeicherten Werts (x vor dem Inkrementieren)
4: Rückgabewert wird x zugewiesen


OK, aber was gibt die Reihenfolge der Schritte 3 und 4 an?
Rup

"Gibt zurück, was X vor dem Inkrement war" ist falsch, siehe mein Update
Tom Brito

In Wirklichkeit sind die Schritte 3 und 4 keine getrennten Operationen - es ist nicht wirklich ein Funktionsaufruf, der einen Wert zurückgibt, es hilft nur, ihn so zu betrachten. Wenn Sie eine Zuordnung haben, wird die rechte Seite "ausgewertet", dann wird das Ergebnis der linken Seite zugewiesen. Das Auswertungsergebnis kann als Rückgabewert betrachtet werden, da es Ihnen hilft, die Reihenfolge der Operationen zu verstehen, aber es ist nicht wirklich .
Jhabbott

Ups, stimmt. Ich meinte die Schritte 2 und 4 - warum wird der zurückgegebene Wert über dem inkrementierten Wert gespeichert?
Rup

1
Dies ist Teil der Definition einer Zuweisungsoperation. Zuerst wird die rechte Seite vollständig ausgewertet, dann wird das Ergebnis der linken Seite zugewiesen.
Jhabbott

1

Wenn sich ++ auf der rechten Seite befindet, wird das Ergebnis zurückgegeben, bevor die Zahl erhöht wird. Wechseln Sie zu ++ x und es wäre in Ordnung gewesen. Java hätte dies optimiert, um eine einzelne Operation (die Zuweisung von x zu x) anstelle des Inkrements auszuführen.


1

Soweit ich sehen kann, tritt der Fehler auf, weil die Zuweisung den inkrementierten Wert mit dem Wert vor der Inkrementierung überschreibt, dh das Inkrement rückgängig macht.

Insbesondere hat der Ausdruck "x ++" vor dem Inkrementieren den Wert "x" im Gegensatz zu "++ x", das nach dem Inkrementieren den Wert "x" hat.

Wenn Sie den Bytecode untersuchen möchten, sehen wir uns die drei fraglichen Zeilen an:

 7:   iload_1
 8:   iinc    1, 1
11:  istore_1

7: iload_1 # Setzt den Wert der 2. lokalen Variablen auf den Stapel
8: iinc 1,1 # erhöht die 2. lokale Variable um 1, beachten Sie, dass der Stapel unberührt bleibt!
9: istore_1 # Legt den Anfang des Stapels ab und speichert den Wert dieses Elements in der 2. lokalen Variablen
(Sie können die Auswirkungen jeder JVM-Anweisung hier lesen ).

Aus diesem Grund wird der obige Code auf unbestimmte Zeit wiederholt, während dies bei der Version mit ++ x nicht der Fall ist. Der Bytecode für ++ x sollte ganz anders aussehen, soweit ich mich an den 1.3 Java-Compiler erinnere, den ich vor etwas mehr als einem Jahr geschrieben habe. Der Bytecode sollte ungefähr so ​​aussehen:

iinc 1,1
iload_1
istore_1

Wenn Sie also nur die beiden ersten Zeilen vertauschen, wird die Semantik so geändert, dass der Wert, der nach dem Inkrement oben im Stapel verbleibt (dh der 'Wert' des Ausdrucks), der Wert nach dem Inkrement ist.


1
    x++
=: (x = x + 1) - 1

Damit:

   x = x++;
=> x = ((x = x + 1) - 1)
=> x = ((x + 1) - 1)
=> x = x; // Doesn't modify x!

Wohingegen

   ++x
=: x = x + 1

Damit:

   x = ++x;
=> x = (x = x + 1)
=> x = x + 1; // Increments x

Natürlich ist das Endergebnis das gleiche wie nur x++;oder ++x;in einer Zeile für sich.


0
 x = x++; (increment is overriden by = )

aufgrund der obigen Aussage erreicht x niemals 3;


0

Ich frage mich, ob die Java-Spezifikation irgendetwas enthält, das das Verhalten genau definiert. (Die offensichtliche Folge dieser Aussage ist, dass ich zu faul bin, um sie zu überprüfen.)

Beachten Sie aus Toms Bytecode, dass die Schlüsselzeilen 7, 8 und 11 sind. Zeile 7 lädt x in den Berechnungsstapel. Zeile 8 erhöht x. Zeile 11 speichert den Wert vom Stapel zurück auf x. In normalen Fällen, in denen Sie sich selbst keine Werte zuweisen, gibt es meines Erachtens keinen Grund, warum Sie nicht laden, speichern und dann erhöhen konnten. Sie würden das gleiche Ergebnis erhalten.

Angenommen, Sie hatten einen normaleren Fall, in dem Sie etwas geschrieben haben wie: z = (x ++) + (y ++);

Ob es hieß (Pseudocode zum Überspringen von technischen Details)

load x
increment x
add y
increment y
store x+y to z

oder

load x
add y
store x+y to z
increment x
increment y

sollte irrelevant sein. Jede Implementierung sollte gültig sein, würde ich denken.

Ich wäre äußerst vorsichtig beim Schreiben von Code, der von diesem Verhalten abhängt. Es sieht für mich sehr implementierungsabhängig aus, zwischen den Rissen in den Spezifikationen. Das einzige Mal, wenn es einen Unterschied macht, ist, wenn Sie etwas Verrücktes gemacht haben, wie im Beispiel hier, oder wenn zwei Threads ausgeführt wurden und von der Reihenfolge der Auswertung innerhalb des Ausdrucks abhängig waren.



0

Der x++Ausdruck ergibt x. Der ++Teil beeinflusst den Wert nach der Auswertung , nicht nach der Anweisung . so x = x++wird effektiv übersetzt in

int y = x; // evaluation
x = x + 1; // increment part
x = y; // assignment

0

Vor dem Erhöhen des Werts um eins wird der Wert der Variablen zugewiesen.


0

Es passiert, weil es inkrementiert ist. Dies bedeutet, dass die Variable inkrementiert wird, nachdem der Ausdruck ausgewertet wurde.

int x = 9;
int y = x++;

x ist jetzt 10, aber y ist 9, der Wert von x, bevor er erhöht wurde.

Weitere Informationen finden Sie unter Definition des Post-Inkrements .


1
Ihr x/ yBeispiel unterscheidet sich vom realen Code und der Unterschied ist relevant. Ihr Link erwähnt nicht einmal Java. Für zwei der Sprachen es nicht erwähnen, ist die Aussage in der Frage nicht definiert.
Matthew Flaschen

0

Überprüfen Sie den folgenden Code,

    int x=0;
    int temp=x++;
    System.out.println("temp = "+temp);
    x = temp;
    System.out.println("x = "+x);

die Ausgabe wird sein,

temp = 0
x = 0

post incrementbedeutet, den Wert zu erhöhen und den Wert vor dem Inkrement zurückzugeben . Deshalb ist der Wert tempist 0. Was ist, wenn temp = iund dies in einer Schleife ist (mit Ausnahme der ersten Codezeile). genau wie in der frage !!!!


-1

Der Inkrementoperator wird auf dieselbe Variable angewendet, der Sie zuweisen. Das bittet um Ärger. Ich bin sicher, dass Sie den Wert Ihrer x-Variablen sehen können, während Sie dieses Programm ausführen. Dies sollte klarstellen, warum die Schleife niemals endet.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.