java - La salida-1 se convierte en una barra en el bucle
string while-loop (3)
Esto es sinceramente bastante extraño, ya que ese código técnicamente nunca debería salir porque ...
int i = 8;
while ((i -= 3) > 0);
... siempre debe resultar en
i
siendo
-1
(8 - 3 = 5; 5 - 3 = 2; 2 - 3 = -1).
Lo que es aún más extraño es que nunca sale en el modo de depuración de mi IDE.
Curiosamente, en el momento en que agrego un cheque antes de la conversión a una
String
, entonces no hay problema ...
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);
}
}
Solo dos puntos de buena práctica de codificación ...
-
En lugar de usar
String.valueOf()
-
Algunos estándares de codificación especifican que los literales de cadena deben ser el objetivo de
.equals()
, en lugar de argumentos, minimizando así NullPointerExceptions.
La única forma en que conseguí que esto no ocurriera fue usando
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);
}
}
... esencialmente parece que Java necesita un poco de tiempo para recuperar el aliento :)
EDITAR: Esto puede ser completamente una coincidencia, pero parece haber cierta correspondencia entre el valor que se está imprimiendo y la tabla ASCII .
-
i
=-1
, el carácter mostrado es/
(valor decimal ASCII de 47) -
i
=-2
, el carácter que se muestra es.
(Valor decimal ASCII de 46) -
i
=-3
, el carácter que se muestra es-
(valor decimal ASCII de 45) -
i
=-4
, el carácter que se muestra es,
(valor decimal ASCII de 44) -
i
=-5
, el carácter que se muestra es+
(valor decimal ASCII de 43) -
i
=-6
, el carácter que se muestra es*
(valor decimal ASCII de 42) -
i
=-7
, el carácter que se muestra es)
(valor decimal ASCII de 41) -
i
=-8
, el carácter que se muestra es(
(valor decimal ASCII de 40) -
i
=-9
, el carácter que se muestra es''
(valor decimal ASCII de 39)
Lo que es realmente interesante es que el carácter en ASCII decimal 48 es el valor
0
y 48 - 1 = 47 (carácter
/
), etc.
Sorprendentemente, el siguiente código sale:
/
-1
El código:
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);
}
}
}
Intenté muchas veces determinar cuántas veces ocurriría esto, pero, desafortunadamente, fue en última instancia incierto, y descubrí que la producción de -2 a veces se convertía en un período. Además, también intenté eliminar el bucle while y la salida -1 sin ningún problema. ¿Quién me puede decir por qué?
Información de versión de JDK:
HopSpot 64-Bit 1.8.0.171
IDEA 2019.1.1
Esto se puede reproducir de manera confiable (o no, dependiendo de lo que desee) con la
openjdk version "1.8.0_222"
(utilizada en mi análisis), OpenJDK
12.0.1
(según Oleksandr Pyrohov) y OpenJDK 13 (según Carlos Heuberger) .
-XX:+PrintCompilation
el código con
-XX:+PrintCompilation
suficientes veces para obtener ambos comportamientos y aquí están las diferencias.
Implementación con errores (muestra la salida):
--- 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
Ejecución correcta (sin visualización):
--- 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)
Podemos notar una diferencia significativa.
Con la ejecución correcta compilamos
test()
dos veces.
Una vez al principio, y una vez más después (presumiblemente porque el JIT nota cuán caliente es el método).
En la
test()
ejecución con errores
test()
se compila (o descompila)
5
veces.
Además, ejecutando con
-XX:-TieredCompilation
(que interpreta o utiliza
C2
)
o
con
-Xbatch
(que obliga a la compilación a ejecutarse en el hilo principal, en lugar de en paralelo), la salida está
garantizada
y con 30000 iteraciones imprime un muchas cosas, por lo que el compilador
C2
parece ser el culpable.
Esto se confirma ejecutando con
-XX:TieredStopAtLevel=1
, que deshabilita
C2
y no produce salida (detenerse en el nivel 4 muestra el error nuevamente).
En la ejecución correcta, el método se compila primero con la compilación de Nivel 3 y luego con el Nivel 4.
En la ejecución con errores, las compilaciones anteriores se descartan (se
made non entrant
) y se compilan nuevamente en el Nivel 3 (que es
C1
, ver enlace anterior).
Por lo tanto, definitivamente es un error en
C2
, aunque no estoy absolutamente seguro de si el hecho de que regrese a la compilación de Nivel 3 lo afecta (y por qué vuelve al nivel 3, todavía hay muchas incertidumbres).
Puede generar el código de ensamblaje con la siguiente línea para profundizar aún más en el agujero del conejo (también vea this para habilitar la impresión de ensamblaje).
java -XX:+PrintCompilation -Xbatch -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly LoopOutPut > broken.asm
En este punto, estoy empezando a quedarme sin habilidades, el comportamiento con errores comienza a exhibirse cuando se descartan las versiones compiladas anteriores, pero las pocas habilidades de ensamblaje que tengo son de los años 90, así que intentaré conseguir un poco de apangin ya que podría ser una de las personas que sabe lo que pasa.
Es probable que ya haya un informe de error al respecto, ya que el código fue presentado al OP por otra persona, y como todo el código C2 no está exento de errores (incluso podría estar relacionado con esto). Espero que este análisis haya sido tan informativo para otros como lo ha sido para mí.
Como el venerable apangin señaló en los comentarios, este es un error reciente . Muy agradecido con todas las personas interesadas y serviciales :)
No sé por qué Java está dando una salida tan aleatoria, pero el problema está en su concatenación que falla para valores más grandes de
i
dentro del ciclo
for
.
Si reemplaza
String value = i + "";
línea con
String value = String.valueOf(i) ;
Su código funciona como se esperaba.
La concatenación que usa
+
para convertir el int en una cadena es nativa y puede tener errores (curiosamente, probablemente la estamos encontrando ahora) y causar tal problema.
Nota: reduje el valor de i inside for loop a 10000 y no tuve problemas con la concatenación.
Este problema se debe informar a las partes interesadas de Java y pueden dar su opinión al respecto.
Editar Actualicé el valor de i in for loop a 3 millones y vi un nuevo conjunto de errores como se muestra a continuación:
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)
Mi versión de Java es la 8.