为了账号安全,请及时绑定邮箱和手机立即绑定

输出-1在循环中变成斜杠

输出-1在循环中变成斜杠

胡说叔叔 2024-01-17 20:57:41
令人惊讶的是,以下代码输出:/-1代码: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);        }    }}我尝试了很多次来确定这种情况会发生多少次,但不幸的是,最终是不确定的,而且我发现 -2 的输出有时会变成句号。另外,我也尝试去掉while循环,输出-1,没有任何问题。谁能告诉我为什么?JDK版本信息:HopSpot 64-Bit 1.8.0.171IDEA 2019.1.1
查看完整描述

3 回答

?
慕标5832272

TA贡献1966条经验 获得超4个赞

openjdk version "1.8.0_222"这可以通过(在我的分析中使用)、OpenJDK 12.0.1(根据 Oleksandr Pyrohov)和 OpenJDK 13(根据 Carlos Heuberger)可靠地重现(或不重现,取决于你想要什么)。


我运行了代码-XX:+PrintCompilation足够多的时间来获得这两种行为,以下是差异。


有缺陷的实现(显示输出):


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

正确运行(无显示):


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

我们可以注意到一个显着的差异。通过正确的执行,我们编译了test()两次。开始时一次,之后再一次(大概是因为 JIT 注意到该方法有多热)。在有 bug 的情况下执行test()会被编译(或反编译)5次。

此外,运行 with -XX:-TieredCompilation(解释或使用C2with -Xbatch(强制编译在主线程中运行,而不是并行运行),输出是有保证的,并且 30000 次迭代会打印出很多东西,所以C2编译器似乎成为罪魁祸首。这可以通过运行 with 来确认-XX:TieredStopAtLevel=1,它会禁用C2并且不会产生输出(在级别 4 停止会再次显示该错误)。

在正确的执行中,该方法首先使用第 3 级编译进行编译,然后再使用第 4 级编译。

在有错误的执行中,先前的编译将被丢弃 ( made non entrant),并再次在第 3 级进行编译(即C1,请参阅前面的链接)。

所以它肯定是 中的一个错误C2,尽管我不确定它返回到 3 级编译的事实是否会影响它(以及为什么它返回到 3 级,仍然有很多不确定性)。

您可以使用以下行生成汇编代码,以更深入地了解兔子洞。

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

在这一点上,我开始耗尽技能,当以前的编译版本被丢弃时,错误行为开始表现出来,但我所拥有的一点汇编技能是来自 90 年代的,所以我会让比我更聪明的人来承担它从这里。

很可能已经有一个关于此的错误报告,因为代码是由其他人提交给OP的,并且所有代码C2都不是没有错误。我希望这个分析对其他人和我一样能提供丰富的信息。



查看完整回答
反对 回复 2024-01-17
?
摇曳的蔷薇

TA贡献1793条经验 获得超6个赞

老实说,这很奇怪,因为从技术上讲,该代码不应该输出,因为......


int i = 8;

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

...应该始终导致i( -18 - 3 = 5; 5 - 3 = 2; 2 - 3 = -1)。更奇怪的是,它从来没有在我的 IDE 的调试模式下输出。


有趣的是,当我在转换为 a 之前添加检查时String,就没有问题了......


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

  }

}

只有两点良好的编码实践......

  1. 而是使用String.valueOf()

  2. 一些编码标准指定字符串文字应该是 的目标.equals(),而不是参数,从而最大限度地减少 NullPointerExceptions。

我避免这种情况发生的唯一方法是使用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);

  }

}

...基本上看起来 Java 需要一点时间来喘口气:)

这可能完全是巧合,但打印出来的值和ASCII Table之间似乎确实存在一些对应关系。

  • i-1,显示的字符为/(ASCII 十进制值为 47)

  • i-2,显示的字符为.(ASCII 十进制值 46)

  • i-3,显示的字符为-(ASCII 十进制值 45)

  • i-4,显示的字符为,(ASCII 十进制值 44)

  • i-5,显示的字符为+(ASCII 十进制值 43)

  • i-6,显示的字符为*(ASCII 十进制值 42)

  • i-7,显示的字符为)(ASCII 十进制值 41)

  • i-8,显示的字符为((ASCII 十进制值 40)

  • i-9,显示的字符为'(ASCII 十进制值为 39)

真正有趣的是,ASCII 十进制 48 处的字符是值0,48 - 1 = 47(字符/),等等......


查看完整回答
反对 回复 2024-01-17
?
DIEA

TA贡献1820条经验 获得超2个赞

不知道为什么 Java 会给出这样的随机输出,但问题在于您的串联对于循环i内的较大值会失败for

如果将String value = i + "";line 替换为String value = String.valueOf(i) ;代码,则可以按预期工作。

用于+将 int 转换为 string 的连接是本机的,可能存在错误(奇怪的是我们现在可能发现它)并导致此类问题。

注意:我将 for 循环中 i 的值减少到 10000,并且没有遇到+连接问题。

这个问题必须报告给 Java 利益相关者,他们可以对此发表意见。

编辑我将 for 循环中 i 的值更新为 300 万,并看到一组新的错误,如下所示:

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版本是8。


查看完整回答
反对 回复 2024-01-17
  • 3 回答
  • 0 关注
  • 42 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信