为啥以 char 作为索引的循环会无限循环?

Posted

技术标签:

【中文标题】为啥以 char 作为索引的循环会无限循环?【英文标题】:Why does a loop with a char as it's index loop infinitely?为什么以 char 作为索引的循环会无限循环? 【发布时间】:2012-09-02 10:41:31 【问题描述】:

这个循环将无限期地继续下去:

char a = 100;
for(a=100; a>=0;--a)
    System.out.println(a);

发生这种情况是因为 a 被提升为算术运算的 int 值并从 16 位 char 值扩大到 32 位,因此将始终保持正数吗?

【问题讨论】:

@oldrinb - 只回答“愿意”。更有趣的问题是“为什么”。 @StephenC 我的评论回答了标题中的问题。 “为什么”我在下面回答。 @Phoenix 我已经更新了我的回复,事实上你是对的,原因是...... :-) 【参考方案1】:

不。 char 值是无符号的 - 当它们低于 0 时,它们会回到 65535。

char 替换为byte - 然后就可以了。

【讨论】:

斯蒂芬,你是对的。我假设它就像 C 的字符。 @NickODell 那么你应该知道 plain char 的签名是实现定义的......它只保证能够适应整个 ASCII 字符集( 0 - 127),C99 确保它可以存储至少 8 位。 oldrinb,在 C 语言中,我总是明确变量是有符号还是无符号,只是为了确保它在所有编译器上都能正常工作。这对于 int 等是否有必要,或者只是偏执狂? @NickODell no -- int 对应于signed intlong 对应于signed long intshort 对应于signed short intchar 的区别在于技术上 plain charsigned charunsigned char 都是独立的数据类型。【参考方案2】:

它确实会无限循环——你所说的原因很接近。这是因为a 不能代表任何满足a >= 0 的数字——char 是无符号的。算术下溢在 Java 中定义明确且未指明。请参阅规范的以下相关部分。

§4.2.2

整数运算符不以任何方式指示上溢或下溢。

这意味着除了比较值之外没有上溢/下溢的迹象......例如如果a --a,则表示发生了下溢。

§15.15.2

在减法之前,对值 1 和变量的值执行二进制数字提升(第 5.6.2 节)。如有必要,可通过缩小原语转换(第 5.1.3 节)和/或在存储变量之前对其类型进行装箱转换(第 5.1.7 节)来缩小差异。前缀递减表达式的值是变量新值被存储后的值。

因此,我们可以看到这里有两个主要步骤:二进制数字提升,然后是窄化原始转换。

§5.6.2

加宽原语转换(第 5.1.2 节)适用于转换以下规则中指定的一个或两个操作数:

如果任一操作数的类型为double,则另一个将转换为double。 否则,如果任一操作数的类型为float,则另一个将转换为float。 否则,如果任一操作数的类型为long,则另一个将转换为long。 否则,两个操作数都转换为int类型。

我们可以看到递减表达式与a一起工作,被视为int,因此执行了扩大转换。这允许它表示值 -1

§5.1.3

窄化基元转换可能会丢失有关数值整体大小的信息,也可能会丢失精度和范围。

...

有符号整数到整数类型 T 的窄化转换只会丢弃除 n 个最低位之外的所有位,其中 n 是用于表示类型的位数T. 除了可能丢失有关数值大小的信息外,这可能会导致结果值的符号与输入值的符号不同。

仅保留 n 个最低位意味着仅保留 int 表达式 a - 1 的最低 16 位。由于这里-10b11111111 11111111 11111111 11111111,所以只保存了较低的0b11111111 11111111。由于char 是无符号的,所有位都对结果有贡献,得到 65535

注意到这里的东西了吗?本质上,这一切都意味着 Java 整数运算是模块化的;在这种情况下,模数是 2^1665536,因为 char 是 16 位数据类型。 -1 (mod 65536) ≡ 65535,所以减量会返回。

【讨论】:

这真的是算术下溢吗?参看。 en.wikipedia.org/wiki/Arithmetic_underflow 你还是搞错了。 Java 中确实会发生整数下溢。只是没有表示 ...正如您引用的 JLS 文本所述。也就是说没有抛出异常,也没有什么可以直接测试看是否发生下溢。【参考方案3】:

正如其他人所说,Java 中的char 类型是无符号的,因此a >= 0 将始终为真。当a 达到 0 然后再次递减时,它变为 65535。如果你只是想反常,你可以这样编写循环以在 101 次迭代后终止:

char a = 100;
for(a=100; a<=100;--a)
    System.out.println(a);

然后,代码审阅者可能会因为写出如此可怕的东西而让您心碎。 :)

【讨论】:

以上是关于为啥以 char 作为索引的循环会无限循环?的主要内容,如果未能解决你的问题,请参考以下文章

为啥这个异步函数无限循环?

为啥当我实现以 2^20 为底的基数排序以对大小为 500 万的数组进行排序时,该程序会陷入无限循环?

为啥错误:重新渲染太多。 React 限制了渲染的数量以防止无限循环。?

为啥这会导致无限循环

为啥这段代码会进入无限循环? [复制]

为啥意外的无限循环会增加 CPU 使用率?