java - La sortie-1 devient une barre oblique dans la boucle




string while-loop (2)

C’est honnêtement assez étrange, car ce code ne devrait techniquement jamais sortir parce que ...

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

... devrait toujours avoir pour résultat i étant -1 (8 - 3 = 5; 5 - 3 = 2; 2 - 3 = -1). Ce qui est encore plus étrange, c'est qu'il ne sort jamais en mode débogage de mon IDE.

Fait intéressant, au moment où j'ajoute un chèque avant la conversion en String , aucun problème ...

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

Juste deux points de bonne pratique de codage ...

  1. Utilisez plutôt String.valueOf()
  2. Certaines normes de codage spécifient que les littéraux de chaîne doivent être la cible de .equals() , plutôt que des arguments, minimisant ainsi NullPointerExceptions.

La seule façon pour que cela ne se produise pas était d'utiliser 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);
  }
}

... essentiellement, il semble que Java ait besoin d'un peu de temps pour reprendre son souffle :)

EDIT: Cela peut être complètement une coïncidence, mais il semble y avoir une certaine correspondance entre la valeur imprimée et la table ASCII .

  • i = -1 , le caractère affiché est / (valeur décimale ASCII de 47)
  • i = -2 , le caractère affiché est . (Valeur décimale ASCII de 46)
  • i = -3 , le caractère affiché est - (valeur décimale ASCII de 45)
  • i = -4 , le caractère affiché est , (valeur décimale ASCII de 44)
  • i = -5 , le caractère affiché est + (valeur décimale ASCII de 43)
  • i = -6 , le caractère affiché est * (valeur décimale ASCII de 42)
  • i = -7 , le caractère affiché est ) (valeur décimale ASCII de 41)
  • i = -8 , le caractère affiché est ( (valeur décimale ASCII de 40)
  • i = -9 , le caractère affiché est ' (valeur décimale ASCII de 39)

Ce qui est vraiment intéressant, c’est que le caractère au point ASCII 48 est la valeur 0 et 48 - 1 = 47 (caractère / ), etc ...

Étonnamment, le code suivant est généré:

/
-1

Le 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);
        }
    }

}

J'ai essayé à plusieurs reprises de déterminer combien de fois cela se produirait, mais malheureusement, c'était finalement incertain, et j'ai trouvé que la sortie de -2 devenait parfois une période. En outre, j'ai également essayé de supprimer la boucle while et la sortie -1 sans aucun problème. Qui peut me dire pourquoi?

Informations de version JDK:

HopSpot 64-Bit 1.8.0.171
IDEA 2019.1.1

Ceci peut être reproduit de manière fiable (ou non, selon ce que vous voulez) avec openjdk version "1.8.0_222" (utilisée dans mon analyse), OpenJDK 12.0.1 (selon Oleksandr Pyrohov) et OpenJDK 13 (selon Carlos Heuberger) .

J'ai exécuté le code avec -XX:+PrintCompilation suffisamment de fois pour obtenir les deux comportements et voici les différences.

Implémentation de buggy (affiche la sortie):

 --- 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

Exécution correcte (pas d'affichage):

 --- 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)

Nous pouvons remarquer une différence significative. Avec la bonne exécution, nous compilons test() deux fois. Une fois au début, et une fois encore (probablement parce que le JIT remarque à quel point la méthode est intéressante). Dans l'exécution de buggy test() est compilé (ou décompilé) 5 fois.

De plus, avec -XX:-TieredCompilation (qui interprète ou utilise C2 ) ou -Xbatch (qui force la compilation à s'exécuter dans le thread principal au lieu de parallèle), la sortie est garantie et, avec 30000 itérations, affiche une beaucoup de choses, donc le compilateur C2 semble être le coupable. Ceci est confirmé en exécutant avec -XX:TieredStopAtLevel=1 , ce qui désactive C2 et ne produit pas de sortie (un arrêt au niveau 4 indique à nouveau le bogue).

Dans l'exécution correcte, la méthode est d'abord compilée avec la compilation de niveau 3 , puis avec le niveau 4.

Dans l’exécution du buggy, les compilations précédentes sont discarées ( made non entrant ) et sont à nouveau compilées au niveau 3 (qui est C1 , voir le lien précédent).

Il s’agit donc bien d’un bogue dans C2 , bien que je ne sois pas absolument certain que le fait de revenir à la compilation de niveau 3 l’affecte (et pourquoi revient-il au niveau 3, alors qu’il ya encore tant d’incertitudes).

Vous pouvez générer le code d'assemblage à l'aide de la ligne suivante pour aller encore plus loin dans le terrier (voir également this pour activer l'impression d'assemblage).

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

À ce stade, je commence à manquer de compétences, le comportement de buggy commence à apparaître lorsque les versions précédentes compilées sont supprimées, mais les compétences d'assemblage que j'ai peu de apangin des apangin 90, je vais donc essayer de mettre la main sur apangin comme il pourrait être l'une des personnes qui sait ce qui se passe.

Il est probable qu'il existe déjà un rapport de bogue à ce sujet, puisque le code a été présenté à l'OP par quelqu'un d'autre et que tout le code C2 n'est pas exempt de bogues (cela pourrait même être lié à cela). J'espère que cette analyse a été aussi informative pour les autres que pour moi.

Comme le vénérable apangin l'a souligné dans les commentaires, il s'agit d'un bug récent . Merci beaucoup à toutes les personnes intéressées et serviables :)





jit