-1 çıktısı döngüde bir eğik çizgi haline gelir


54

Şaşırtıcı bir şekilde, aşağıdaki kod çıktısı:

/
-1

Kod:

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

}

Bunun kaç kez olacağını belirlemek için birçok kez denedim, ama ne yazık ki, sonuçta belirsizdi ve -2 çıktısının bazen bir döneme dönüştüğünü buldum. Ayrıca, while döngüsü ve çıktı -1 herhangi bir sorun olmadan kaldırmaya çalıştım. Kim bana nedenini söyleyebilir?


JDK sürüm bilgileri:

HopSpot 64-Bit 1.8.0.171
IDEA 2019.1.1

2
Yorumlar uzun tartışmalar için değildir; bu sohbet sohbete taşındı .
Samuel Liew

Yanıtlar:


36

Bu, ( openjdk version "1.8.0_222"analizimde kullanılan), OpenJDK 12.0.1(Oleksandr Pyrohov'a göre) ve OpenJDK 13 (Carlos Heuberger'e göre ) ile güvenilir bir şekilde çoğaltılabilir (veya ne istediğinize bağlı olarak çoğaltılamaz ).

Her -XX:+PrintCompilationiki davranışı almak için yeterli kez kodu koştu ve işte farklılıklar.

Buggy uygulaması (çıktıyı görüntüler):

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

Doğru çalışma (ekran yok):

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

Önemli bir fark görebiliriz. Doğru uygulama ile test()iki kez derleriz. Bir kez başlangıçta ve daha sonra bir kez daha (muhtemelen JIT, yöntemin ne kadar sıcak olduğunu fark ettiği için). Buggy yürütme 5 kez test()derlenir (veya ayrıştırılır) .

Ayrıca, birlikte çalıştığı -XX:-TieredCompilation(ki ya yorumladığını veya kullanımları C2) ya sahip -Xbatch(paralel ana iş parçacığı çalıştırmak için derleme zorlar ki, yerine), çıkış olduğunu garanti böylece ve bir sürü malzeme dışarı 30000 yineleme baskılar ile C2derleyici görünüyor suçlu olmak. Bu, çıktıyı -XX:TieredStopAtLevel=1devre dışı bırakan C2ve üretmeyen (ile 4. seviyede durmak hatayı tekrar gösterir) ile çalışarak onaylanır .

Doğru yürütmede, yöntem önce Seviye 3 derlemesi ile, daha sonra Seviye 4 ile derlenir .

Buggy yürütmesinde, önceki derlemeler çıkarılır ( made non entrant) ve yine Düzey 3'te derlenir (yani C1önceki bağlantıya bakın).

Bu yüzden kesinlikle bir hata C2, ancak Seviye 3 derlemesine geri dönmesinin gerçeği etkilediğinden emin değilim (ve neden hala 3. seviyeye geri dönüyor, hala birçok belirsizlik var).

Sen tavşan deliğinden daha derinlere inmeye için aşağıdaki satırla montaj kodu oluşturabilirsiniz (ayrıca bkz bu baskı düzeneği etkinleştirmek için).

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

Bu noktada beceriler tükenmeye başlıyorum, buggy davranışı önceki derlenmiş sürümler atıldığında sergilenmeye başlıyor, ancak 90'lardan ne kadar az montaj becerisine sahibim, bu yüzden benden daha akıllı birine izin vereceğim buradan.

Bu kod zaten OP'ye başka biri tarafından sunulduğundan ve C2 kodunun hatasız olmadığı için bu konuda zaten bir hata raporu olması muhtemeldir . Umarım bu analiz bana olduğu kadar başkalarına da bilgilendirici olmuştur.

Saygıdeğer apangin yorumlarda belirtildiği gibi, bu son zamanlarda yapılan bir hatadır . İlgilenen ve yardımcı olan herkese çok yükümlü :)


Ayrıca C2- JitWatch kullanarak oluşturulan derleyici koduna baktım (ve anlamaya çalıştım) - C1üretilen kod hala bayt koduna benziyor C2, tamamen farklı ( i8 ile başlatma bile bulamadım )
user85421-Banned

cevabınız çok iyi, denedim, c2'yi devre dışı bıraktım, sonuç doğru. Ancak, gerçek projenin yukarıdaki kodu olmayacak olsa da, genel olarak, bu parametrelerin çoğu projede varsayılan olarak bulunur, ancak proje benzer kod kullanıyorsa, muhtemelen korkunçtur
okali

1
@Eugene bu oldukça zor oldu, ben tutulma derleyici hata ya da benzeri gibi bir şey olacağından emindim ... ve ilk başta da
çoğaltamadım

1
@Kayaman kabul etti. Yaptığınız analiz çok iyi, apangin'in bunu açıklamak ve düzeltmek için daha fazlası olmalı. Trende ne muhteşem bir sabah!
Eugene

7
Bu konuyu sadece yanlışlıkla fark ettim. Soruyu gördüğümden emin olmak için, @mentions kullanın veya #jvm etiketi ekleyin. İyi analiz, BTW. Bu gerçekten bir C2 derleyici hatası, sadece birkaç gün önce düzeltildi - JDK-8231988 .
apangin

4

Bu kod teknik olarak asla çıkmaması gerektiği için bu oldukça garip, çünkü ...

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

... her sonuçlanmalıdır iolmak -1(8 - 3 = 5; 5 - 3 = 2, 2 - 3 = 1). Hatta tuhaf olan şey, IDE'imin hata ayıklama modunda asla çıkmamasıdır.

İlginç bir şekilde, dönüşümden önce bir çek eklediğim anda String, o zaman sorun yok ...

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

İyi kodlama uygulamalarından sadece iki nokta ...

  1. Oldukça kullanım String.valueOf()
  2. Bazı kodlama standartları, String değişmezlerinin .equals()argüman yerine hedef olması gerektiğini belirtir ve böylece NullPointerExceptions işlevini en aza indirir.

Bunun olmamasını sağlamanın tek yolu 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);
  }
}

... aslında Java'nın nefesini yakalamak için biraz zamana ihtiyacı var gibi görünüyor :)

DÜZENLEME: Bu tamamen rastlantısal olabilir, ancak yazdırılan değer ile ASCII Tablosu arasında bazı yazışmalar olduğu görülmektedir .

  • i= -1, gösterilen karakter /(47 ASCII ondalık değeri)
  • i= -2, görüntülenen karakter .(ASCII ondalık değeri 46)
  • i= -3, görüntülenen karakter -(ASCII ondalık değeri 45)
  • i= -4, görüntülenen karakter ,(ASCII ondalık değeri 44)
  • i= -5, görüntülenen karakter +(ASCII ondalık değeri 43)
  • i= -6, görüntülenen karakter *(ASCII ondalık değeri 42)
  • i= -7, görüntülenen karakter )(ASCII ondalık değeri 41)
  • i= -8, görüntülenen karakter ((ASCII ondalık değeri 40)
  • i= -9, görüntülenen karakter '(ASCII ondalık değeri 39)

Asıl ilginç olan, ASCII ondalık 48'deki karakterin değer 0ve 48 - 1 = 47 (karakter /) vb.


1
"/" karakterinin sayısal değeri "-1" dir ??? bu nereden geliyor? ( (int)'/' == 47; (char)-10xFFFF
unicode'da

1
char c = '/'; int a = Character.getNumericValue (c); System.out.println (a) '
Ambro-r

getNumericValue()verilen kodla nasıl ilişkilidir ??? ve nasıl dönüştürmek yok -1etmek '/'??? Neden olmasın '-', getNumericValue('-')aynı zamanda -1??? (BTW birçok yöntem geri döner -1)
user85421-Banned

@CarlosHeuberger, karakter değerini almak için ( ) getNumericValue()üzerinde çalışıyordum . ASCII ondalık değerinin 47 olması gerektiği konusunda % 100 doğruydunuz (beklediğim buydu), ancak eklediğim gibi -1 bu noktada dönüyordu . Bahsettiğiniz karışıklığı görebiliyorum ve gönderiyi güncellediniz. value//getNumericValue()System.out.println(Character.getNumericValue(value.toCharArray()[0]));
Ambro-r

1

Java'nın neden bu kadar rasgele çıktı verdiğini bilmiyorum, ancak sorun döngü iiçinde daha büyük değerler için başarısız oluyor for.

Eğer kod String value = i + "";ile satır yerine String value = String.valueOf(i) ;beklendiği gibi çalışır.

+İnt dizesine dönüştürmek için kullanılan birleştirme yereldir ve buggy olabilir (garip bir şekilde şimdi kuruyoruz) ve böyle bir soruna neden oluyor.

Not: Döngü için içindeki i değerini 10000'e düşürdüm ve +birleştirme ile ilgili sorunla karşılaşmadım .

Bu sorun Java paydaşlarına bildirilmelidir ve aynı fikirlerini verebilirler.

Düzenleme için i in değerini 3 milyona güncelledim ve aşağıdaki gibi yeni bir hata kümesi gördüm:

Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: -1
    at java.lang.Integer.getChars(Integer.java:463)
    at java.lang.Integer.toString(Integer.java:402)
    at java.lang.String.valueOf(String.java:3099)
    at solving.LoopOutPut.test(LoopOutPut.java:16)
    at solving.LoopOutPut.main(LoopOutPut.java:8)

Java sürümüm 8.


1
Dize birleştirme yerel olduğunu sanmıyorum - sadece StringConcatFactory(OpenJDK 13) veya StringBuilder(Java 8) kullanır
user85421-Yasaklandı

@CarlosHeuberger Mümkün. StringConcatFactory Sınıf olması gerekiyorsa java 9'dan geldiğini düşünüyorum . ama bildiğim kadarıyla java kadar java 8 java don; t destek operatörü aşırı yükleme
Vinay Prajapati

@Vinay, bunu da denedim ve evet işe yarıyor, ancak döngüyü 30000'den 3000000'e yükselttiğiniz anda aynı sorunu yaşamaya başlıyorsunuz.
Ambro-r

@ Ambro-r Önerilen değerle denedim ve Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: -1hata alıyorum. Garip.
Vinay Prajapati

3
i + ""tam olarak new StringBuilder().append(i).append("").toString()Java 8'de olduğu gibi derlenir ve sonunda bunu da çıktı üretir
user85421-Banned
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.