Niespodziewanie następujące kody wyjściowe:
/
-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);
}
}
}
Wielokrotnie próbowałem ustalić, ile razy miałoby to nastąpić, ale niestety ostatecznie było to niepewne i stwierdziłem, że wyjście -2 czasami zamieniało się w kropkę. Ponadto próbowałem również usunąć pętlę while i wyjście -1 bez żadnych problemów. Kto może mi powiedzieć dlaczego?
Informacje o wersji JDK:
HopSpot 64-Bit 1.8.0.171
IDEA 2019.1.1
Odpowiedzi:
Można to rzetelnie odtworzyć (lub nie odtworzyć, w zależności od tego, czego chcesz) za pomocą
openjdk version "1.8.0_222"
(zastosowanego w mojej analizie), OpenJDK12.0.1
(według Oleksandra Pyrohova) i OpenJDK 13 (według Carlosa Heubergera).Uruchomiłem kod z
-XX:+PrintCompilation
wystarczającą ilością razy, aby uzyskać oba zachowania, a oto różnice.Implementacja błędna (wyświetla dane wyjściowe):
Prawidłowy przebieg (bez wskazania):
Możemy zauważyć jedną istotną różnicę. Przy poprawnym wykonaniu kompilujemy
test()
dwa razy. Raz na początku i jeszcze raz później (prawdopodobnie dlatego, że JIT zauważa, jak gorąca jest ta metoda). W buggy wykonanietest()
jest kompilowane (lub dekompilowane) 5 razy.Dodatkowo, uruchamianie z
-XX:-TieredCompilation
(który interpretuje lub używaC2
) lub z-Xbatch
(co zmusza kompilację do uruchamiania w głównym wątku, zamiast równolegle), wyjście jest gwarantowane, a przy 30000 iteracjach drukuje wiele rzeczy, więcC2
kompilator wydaje się być winowajcą. Potwierdza to uruchomienie z-XX:TieredStopAtLevel=1
, które wyłączaC2
i nie generuje danych wyjściowych (zatrzymanie na poziomie 4 ponownie pokazuje błąd).W prawidłowym wykonaniu metoda jest najpierw kompilowana z kompilacją poziomu 3 , a następnie z poziomem 4.
W wykonaniu buggy poprzednie kompilacje są ignorowane (
made non entrant
) i ponownie kompilowane na poziomie 3 (czyliC1
patrz poprzedni link).Więc na pewno jest to błąd
C2
, chociaż nie jestem absolutnie pewien, czy wpływa na to fakt, że wraca do kompilacji poziomu 3 (i dlaczego wraca do poziomu 3, wciąż jest tak wiele niepewności).Można wygenerować kod montażowej z następującą linię iść nawet głębiej do króliczej nory (patrz również to , aby umożliwić montaż drukowania).
W tym momencie zaczynam brakować umiejętności, zachowanie buggy zaczyna się ujawniać, gdy poprzednie kompilowane wersje są odrzucane, ale to, co mam małe umiejętności montażowe, mam z lat 90-tych, więc pozwolę, by ktoś mądrzejszy ode mnie wziął stąd.
Prawdopodobnie jest już raport o błędzie na ten temat, ponieważ kod został przedstawiony OP przez kogoś innego, a ponieważ cały kod C2 nie jest bez błędów . Mam nadzieję, że ta analiza była tak samo pouczająca dla innych, jak dla mnie.
Jak zauważył czcigodny apangin w komentarzach, jest to ostatni błąd . Bardzo zobowiązany do wszystkich zainteresowanych i pomocnych osób :)
źródło
C2
- spojrzałem na wygenerowany kod asemblera (i spróbowałem go zrozumieć) za pomocą JitWatch -C1
wygenerowany kod nadal przypomina kod bajtowy,C2
jest zupełnie inny (nie mogłem nawet znaleźć inicjalizacjii
z 8)To jest naprawdę dość dziwne, ponieważ ten kod technicznie nigdy nie powinien być generowany, ponieważ ...
... powinno zawsze skutkować
i
byciem-1
(8 - 3 = 5; 5 - 3 = 2; 2 - 3 = -1). Jeszcze dziwniejsze jest to, że nigdy nie wychodzi ono w trybie debugowania mojego IDE.Co ciekawe, w momencie, gdy dodam czek przed konwersją na
String
, to nie ma problemu ...Tylko dwa punkty dobrej praktyki kodowania ...
String.valueOf()
.equals()
, a nie argumentem, minimalizując w ten sposób wyjątki NullPointerExceptions.Jedynym sposobem, aby to się nie wydarzyło, było użycie
String.format()
... w zasadzie wygląda na to, że Java potrzebuje trochę czasu, aby złapać oddech :)
EDYCJA: Może to być całkowicie przypadkowa, ale wydaje się, że istnieje pewna zgodność między wartością, która jest drukowana, a tabelą ASCII .
i
=-1
, wyświetlany znak to/
(wartość dziesiętna ASCII 47)i
=-2
, wyświetlany znak to.
(wartość dziesiętna ASCII 46)i
=-3
, wyświetlany znak to-
(wartość dziesiętna ASCII 45)i
=-4
, wyświetlany znak to,
(wartość dziesiętna ASCII 44)i
=-5
, wyświetlany znak to+
(wartość dziesiętna ASCII 43)i
=-6
, wyświetlany znak to*
(wartość dziesiętna ASCII 42)i
=-7
, wyświetlany znak to)
(wartość dziesiętna ASCII 41)i
=-8
, wyświetlany znak to(
(wartość dziesiętna ASCII 40)i
=-9
, wyświetlany znak to'
(wartość dziesiętna ASCII 39)Naprawdę interesujące jest to, że znak w postaci dziesiętnej 48 ASCII jest wartością,
0
a 48 - 1 = 47 (znak/
) itp.źródło
(int)'/' == 47
;(char)-1
jest niezdefiniowany0xFFFF
to <nie znak> w Unicode)getNumericValue()
odnosi się do danego kodu? i jak przekształca-1
się w'/'
??? Dlaczego nie'-'
,getNumericValue('-')
jest też-1
??? (BTW zwraca wiele metod-1
)getNumericValue()
navalue
(/
), aby uzyskać wartość znaku. Masz 100% rację, że wartość dziesiętna ASCII/
powinna wynosić 47 (tego też się spodziewałam), alegetNumericValue()
zwracała -1 w tym momencie, jak dodałemSystem.out.println(Character.getNumericValue(value.toCharArray()[0]));
. Widzę zamieszanie, o którym mówisz, i zaktualizowałem post.Nie wiem, dlaczego Java daje tak losowe dane wyjściowe, ale problem polega na tym, że konkatenacja kończy się niepowodzeniem w przypadku większych wartości
i
wewnątrzfor
pętli.Jeśli zastąpisz
String value = i + "";
wierszString value = String.valueOf(i) ;
kodem, zadziała zgodnie z oczekiwaniami.Łączenie za pomocą
+
konwersji int na ciąg znaków jest rodzime i może być wadliwe (prawdopodobnie dziwnie go teraz znajdujemy) i powoduje taki problem.Uwaga: Zmniejszyłem wartość i inside for loop do 10000 i nie napotkałem problemu z
+
konkatenacją.Ten problem należy zgłosić interesariuszom Java i mogą wyrazić swoją opinię na ten temat.
Edytuj Zaktualizowałem wartość i dla pętli do 3 milionów i zobaczyłem nowy zestaw błędów, jak poniżej:
Moja wersja Java to 8.
źródło
StringConcatFactory
(OpenJDK 13) lubStringBuilder
(Java 8)StringConcatFactory
klasa. ale o ile wiem, java do java 8 java; t wspiera przeciążanie operatoraException in thread "main" java.lang.ArrayIndexOutOfBoundsException: -1
błąd. Dziwne.i + ""
jest skompilowany dokładnie tak, jaknew StringBuilder().append(i).append("").toString()
w Javie 8, a użycie tego ostatecznie generuje również wynik