java - Die Ausgabe-1 wird zu einem Schrägstrich in der Schleife




string while-loop (2)

Das ist ehrlich gesagt ziemlich merkwürdig, da dieser Code technisch nie ausgegeben werden sollte, weil ...

int i = 8;
while ((i -= 3) > 0);

... sollte immer zu i = -1 (8 - 3 = 5; 5 - 3 = 2; 2 - 3 = -1). Was noch seltsamer ist, ist, dass es niemals im Debug-Modus meiner IDE ausgegeben wird.

Interessanterweise ist es in dem Moment, in dem ich einen Check vor der Konvertierung in einen String hinzufüge, kein Problem ...

public void test() {
  int i = 8;
  while ((i -= 3) > 0);
  if(i != -1) { System.out.println("Not -1"); }
  String value = String.valueOf(i);
  if (!"-1".equalsIgnoreCase(value)) {
    System.out.println(value);
    System.out.println(i);
  }
}

Nur zwei Punkte guter Codierungspraxis ...

  1. Verwenden String.valueOf() lieber String.valueOf()
  2. Einige Codierungsstandards legen fest, dass .equals() das Ziel von .equals() und nicht von Argumenten sein sollen, wodurch NullPointerExceptions minimiert werden.

Die einzige Möglichkeit, dass dies nicht auftritt, war die Verwendung von String.format()

public void test() {
  int i = 8;
  while ((i -= 3) > 0);
  String value = String.format("%d", i);
  if (!"-1".equalsIgnoreCase(value)) {
    System.out.println(value);
    System.out.println(i);
  }
}

... im Grunde sieht es so aus, als ob Java ein bisschen Zeit braucht, um zu Atem zu kommen :)

BEARBEITEN: Dies mag völlig zufällig sein, aber es scheint eine gewisse Übereinstimmung zwischen dem auszudruckenden Wert und der ASCII-Tabelle zu geben .

  • i = -1 , das angezeigte Zeichen ist / (ASCII-Dezimalwert von 47)
  • i = -2 , das angezeigte Zeichen ist . (ASCII-Dezimalwert von 46)
  • i = -3 , angezeigtes Zeichen ist - (ASCII-Dezimalwert von 45)
  • i = -4 , das angezeigte Zeichen ist (ASCII-Dezimalwert von 44)
  • i = -5 , das angezeigte Zeichen ist + (ASCII-Dezimalwert von 43)
  • i = -6 , das angezeigte Zeichen ist * (ASCII-Dezimalwert von 42)
  • i = -7 , angezeigtes Zeichen ist ) (ASCII-Dezimalwert von 41)
  • i = -8 , angezeigtes Zeichen ist ( (ASCII-Dezimalwert von 40)
  • i = -9 , angezeigtes Zeichen ist ' (ASCII-Dezimalwert von 39)

Was wirklich interessant ist, ist, dass das Zeichen bei ASCII-Dezimalzahl 48 den Wert 0 und 48 - 1 = 47 (Zeichen / ) usw. hat.

Überraschenderweise wird der folgende Code ausgegeben:

/
-1

Der Code:

public class LoopOutPut {

    public static void main(String[] args) {
        LoopOutPut loopOutPut = new LoopOutPut();
        for (int i = 0; i < 30000; i++) {
            loopOutPut.test();
        }

    }

    public void test() {
        int i = 8;
        while ((i -= 3) > 0) ;
        String value = i + "";
        if (!value.equals("-1")) {
            System.out.println(value);
            System.out.println(i);
        }
    }

}

Ich habe viele Male versucht festzustellen, wie oft dies passieren würde, aber leider war es letztendlich ungewiss, und ich stellte fest, dass die Ausgabe von -2 manchmal zu einer Periode wurde. Außerdem habe ich versucht, die while-Schleife zu entfernen und -1 ohne Probleme auszugeben. Wer kann mir sagen warum?

Informationen zur JDK-Version:

HopSpot 64-Bit 1.8.0.171
IDEA 2019.1.1

Dies kann mit openjdk version "1.8.0_222" (in meiner Analyse verwendet), OpenJDK 12.0.1 (nach Oleksandr Pyrohov) und OpenJDK 13 (nach Carlos Heuberger) zuverlässig reproduziert (oder nicht reproduziert werden, je nachdem, was Sie wollen) .

Ich habe den Code mit -XX:+PrintCompilation genügend oft ausgeführt, um beide Verhaltensweisen zu ermitteln, und hier sind die Unterschiede.

Fehlerhafte Implementierung (zeigt die Ausgabe an):

 --- Previous lines are identical in both
 54   17       3       java.lang.AbstractStringBuilder::<init> (12 bytes)
 54   23       3       LoopOutPut::test (57 bytes)
 54   18       3       java.lang.String::<init> (82 bytes)
 55   21       3       java.lang.AbstractStringBuilder::append (62 bytes)
 55   26       4       java.lang.AbstractStringBuilder::ensureCapacityInternal (27 bytes)
 55   20       3       java.lang.StringBuilder::<init> (7 bytes)
 56   19       3       java.lang.StringBuilder::toString (17 bytes)
 56   25       3       java.lang.Integer::getChars (131 bytes)
 56   22       3       java.lang.StringBuilder::append (8 bytes)
 56   27       4       java.lang.String::equals (81 bytes)
 56   10       3       java.lang.AbstractStringBuilder::ensureCapacityInternal (27 bytes)   made not entrant
 56   28       4       java.lang.AbstractStringBuilder::append (50 bytes)
 56   29       4       java.lang.String::getChars (62 bytes)
 56   24       3       java.lang.Integer::stringSize (21 bytes)
 58   14       3       java.lang.String::getChars (62 bytes)   made not entrant
 58   33       4       LoopOutPut::test (57 bytes)
 59   13       3       java.lang.AbstractStringBuilder::append (50 bytes)   made not entrant
 59   34       4       java.lang.Integer::getChars (131 bytes)
 60    3       3       java.lang.String::equals (81 bytes)   made not entrant
 60   30       4       java.util.Arrays::copyOfRange (63 bytes)
 61   25       3       java.lang.Integer::getChars (131 bytes)   made not entrant
 61   32       4       java.lang.String::<init> (82 bytes)
 61   16       3       java.util.Arrays::copyOfRange (63 bytes)   made not entrant
 61   31       4       java.lang.AbstractStringBuilder::append (62 bytes)
 61   23       3       LoopOutPut::test (57 bytes)   made not entrant
 61   33       4       LoopOutPut::test (57 bytes)   made not entrant
 62   35       3       LoopOutPut::test (57 bytes)
 63   36       4       java.lang.StringBuilder::append (8 bytes)
 63   18       3       java.lang.String::<init> (82 bytes)   made not entrant
 63   38       4       java.lang.StringBuilder::append (8 bytes)
 64   21       3       java.lang.AbstractStringBuilder::append (62 bytes)   made not entrant

Richtiger Lauf (keine Anzeige):

 --- Previous lines identical in both
 55   23       3       LoopOutPut::test (57 bytes)
 55   17       3       java.lang.AbstractStringBuilder::<init> (12 bytes)
 56   18       3       java.lang.String::<init> (82 bytes)
 56   20       3       java.lang.StringBuilder::<init> (7 bytes)
 56   21       3       java.lang.AbstractStringBuilder::append (62 bytes)
 56   26       4       java.lang.AbstractStringBuilder::ensureCapacityInternal (27 bytes)
 56   19       3       java.lang.StringBuilder::toString (17 bytes)
 57   22       3       java.lang.StringBuilder::append (8 bytes)
 57   24       3       java.lang.Integer::stringSize (21 bytes)
 57   25       3       java.lang.Integer::getChars (131 bytes)
 57   27       4       java.lang.String::equals (81 bytes)
 57   28       4       java.lang.AbstractStringBuilder::append (50 bytes)
 57   10       3       java.lang.AbstractStringBuilder::ensureCapacityInternal (27 bytes)   made not entrant
 57   29       4       java.util.Arrays::copyOfRange (63 bytes)
 60   16       3       java.util.Arrays::copyOfRange (63 bytes)   made not entrant
 60   13       3       java.lang.AbstractStringBuilder::append (50 bytes)   made not entrant
 60   33       4       LoopOutPut::test (57 bytes)
 60   34       4       java.lang.Integer::getChars (131 bytes)
 61    3       3       java.lang.String::equals (81 bytes)   made not entrant
 61   32       4       java.lang.String::<init> (82 bytes)
 62   25       3       java.lang.Integer::getChars (131 bytes)   made not entrant
 62   30       4       java.lang.AbstractStringBuilder::append (62 bytes)
 63   18       3       java.lang.String::<init> (82 bytes)   made not entrant
 63   31       4       java.lang.String::getChars (62 bytes)

Wir können einen signifikanten Unterschied feststellen. Bei korrekter Ausführung kompilieren wir test() zweimal. Einmal am Anfang und noch einmal danach (vermutlich, weil die JIT bemerkt, wie heiß die Methode ist). Im Buggy wird die test() ) 5 mal kompiliert (oder dekompiliert).

Bei Ausführung mit -XX:-TieredCompilation (das entweder C2 interpretiert oder verwendet) oder mit -Xbatch (das die Ausführung der Kompilierung im Hauptthread anstelle von parallel erzwingt) wird die Ausgabe garantiert, und bei 30000 Iterationen wird a gedruckt viele Sachen, so scheint der C2 Compiler der Schuldige zu sein. Dies wird durch Ausführen mit -XX:TieredStopAtLevel=1 , wodurch C2 deaktiviert wird und keine Ausgabe erzeugt wird (das Stoppen bei Stufe 4 zeigt den Fehler erneut).

Bei korrekter Ausführung wird die Methode zuerst mit der Level 3- Kompilierung und anschließend mit Level 4 kompiliert.

In der Buggy-Ausführung werden die vorherigen Kompilierungen verworfen ( made non entrant einbezogen) und erneut auf Stufe 3 kompiliert ( C1 , siehe vorherigen Link).

Es ist also definitiv ein Fehler in C2 , obwohl ich nicht sicher bin, ob die Tatsache, dass es zurück zu Level 3 Kompilierung geht, ihn beeinflusst (und warum es zurück zu Level 3 geht, so viele Unsicherheiten immer noch).

Sie können den Assembler-Code mit der folgenden Zeile generieren, um noch tiefer in das Kaninchenloch hineinzugehen (sehen Sie this , um den Assembler-Druck zu ermöglichen).

java -XX:+PrintCompilation -Xbatch -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly LoopOutPut > broken.asm

An diesem Punkt werden mir die Fähigkeiten ausgehen, das Verhalten des Buggys zeigt sich, wenn die zuvor kompilierten Versionen verworfen werden, aber meine geringen Assemblerfähigkeiten stammen aus den 90er Jahren, also werde ich versuchen, apangin in den Griff zu bekommen Vielleicht ist er einer der Leute, die wissen, was los ist.

Es ist wahrscheinlich, dass es bereits einen Fehlerbericht darüber gibt, da der Code dem OP von einer anderen Person vorgelegt wurde und da der gesamte Code C2 nicht fehlerfrei ist (das könnte sogar damit zusammenhängen). Ich hoffe, diese Analyse war für andere so informativ wie für mich.

Wie der ehrwürdige Apangin in den Kommentaren betonte, handelt es sich hierbei um einen kürzlich aufgetretenen Fehler . Vielen Dank an alle interessierten und hilfsbereiten Leute :)





jit