【问题标题】:Why does int i = 1024 * 1024 * 1024 * 1024 compile without error?为什么 int i = 1024 * 1024 * 1024 * 1024 编译没有错误?
【发布时间】:2014-08-31 19:38:40
【问题描述】:

int 的限制是从 -2147483648 到 2147483647。

如果我输入

int i = 2147483648;

那么Eclipse会在“2147483648”下面提示一个红色下划线。

但如果我这样做:

int i = 1024 * 1024 * 1024 * 1024;

它会编译得很好。

public class Test {
    public static void main(String[] args) {        

        int i = 2147483648;                   // error
        int j = 1024 * 1024 * 1024 * 1024;    // no error

    }
}

也许这是Java中的一个基本问题,但我不知道为什么第二个变体不会产生错误。

【问题讨论】:

  • 即使编译器通常会将计算“折叠”为单个值作为优化,但如果结果溢出,它也不会这样做,因为任何优化都不应该改变程序的行为.
  • 它不能解释2147483648:这个字面没有意义。
  • 而且 Java 不会报告整数溢出——操作会默默地“失败”。
  • @JacobKrall:无论是否启用了检查性,C# 都会将此报告为缺陷;除非在未经检查的区域内,否则所有仅包含常量表达式的计算都会自动检查。
  • 我不鼓励你在 StackOverflow 上问“为什么不”的问题;他们很难回答。 “为什么不”的问题预设了世界显然应该是这样的,它不是这样的,并且需要有充分的理由让它这样。这个假设几乎永远不会成立。一个更精确的问题是“规范的哪一部分描述了如何计算常量整数算术?”或“Java 中如何处理整数溢出?”

标签: java int


【解决方案1】:

1024 * 1024 * 1024 * 1024 和 2147483648 在 Java 中没有相同的值。

实际上,2147483648 甚至不是一个值(尽管 2147483648L 是)在 Java 中。编译器实际上不知道它是什么,也不知道如何使用它。所以它会发牢骚。

1024 在 Java 中是一个有效的 int,一个有效的 int 乘以另一个有效的 int,始终是一个有效的 int。即使它与您直观期望的值不同,因为计算会溢出。

示例

考虑以下代码示例:

public static void main(String[] args) {
    int a = 1024;
    int b = a * a * a * a;
}

您希望这会产生编译错误吗?现在变得更滑了。
如果我们放置一个循环 3 次迭代并在循环中相乘会怎样?

允许编译器进行优化,但它不能改变程序的行为。


有关此案实际处理方式的一些信息:

在 Java 和许多其他语言中,整数将由固定数量的位组成。不适合给定位数的计算将overflow;计算基本上在 Java 中执行 modulus 2^32,然后将值转换回 signed 整数。

其他语言或 API 使用动态位数(Java 中为 BigInteger),引发异常或将值设置为诸如 not-a-number 之类的魔法值。

【讨论】:

  • 对我来说,您的声明“2147483648 甚至不是一个值(尽管2147483648L 是)”真的 巩固了@arshajii 试图做到的观点制作。
  • 啊,对不起,是的,那是我。我在您的回答中遗漏了溢出/模运算的概念。请注意,如果您不同意我的修改,可以回滚。
  • @owlstead 您的编辑实际上是正确的。我不包括它的原因是:不管1024 * 1024 * 1024 * 1024 是如何处理的,我真的想强调它与写2147473648 不是一回事。一种语言可以通过多种方式(您已经列出了一些)来处理它。它被合理地分开,并且有用。所以我会离开它。当您对热门问题有高排名的答案时,大量信息变得越来越重要。
【解决方案2】:

答:因为这不是错误。

背景: 乘法1024 * 1024 * 1024 * 1024 会导致溢出。溢出通常是一个错误。发生溢出时,不同的编程语言会产生不同的行为。例如,C和C++对有符号整数称其为“未定义行为”,行为定义为无符号整数(取数学结果,只要结果为负就加UINT_MAX + 1,只要结果就减去UINT_MAX + 1大于UINT_MAX)。

在 Java 的情况下,如果具有 int 值的操作的结果不在允许的范围内,则从概念上讲,Java 加或减 2^32 直到结果在允许的范围内。所以该声明是完全合法的,没有错误。它只是不会产生您可能希望的结果。

你当然可以争论这种行为是否有用,以及编译器是否应该给你一个警告。我个人认为警告会非常有用,但错误是不正确的,因为它是合法的 Java。

【讨论】:

    【解决方案3】:

    这句话没有错;您只是将 4 个数字相乘并将其分配给一个 int,恰好发生了溢出。这与分配单个 literal 不同,后者将在编译时进行边界检查。

    导致错误的是越界literal,而不是assignment:

    System.out.println(2147483648);        // error
    System.out.println(2147483647 + 1);    // no error
    

    相比之下,long 文字可以很好地编译:

    System.out.println(2147483648L);       // no error
    

    请注意,事实上,结果 仍然在编译时计算,因为 1024 * 1024 * 1024 * 1024 是 constant expression:

    int i = 1024 * 1024 * 1024 * 1024;
    

    变成:

       0: iconst_0      
       1: istore_1      
    

    请注意,结果 (0) 只是简单地加载和存储,不会发生乘法。


    来自JLS §3.10.1(感谢@ChrisK 在 cmets 中提出):

    如果int 类型的十进制文字大于2147483648 (231),或者如果十进制文字2147483648 出现在任何地方,则会出现编译时错误除了作为一元减号运算符 (§15.15.4) 的操作数。

    【讨论】:

    • 对于乘法,JLS 说,如果整数乘法溢出,则结果是数学乘积的低位,以某种足够大的二进制补码格式表示。因此,如果发生溢出,则结果的符号可能与两个操作数值的数学乘积的符号不同。
    • 优秀的答案。有些人似乎认为溢出是某种错误或失败,但事实并非如此。
    • @iowatiger08 语言语义由独立于 JVM 的 JLS 概述(因此使用哪个 JVM 无关紧要)。
    • @WouterLievens,溢出 is 通常是“不寻常”的情况,如果不是彻底的错误情况。这是有限精度数学的结果,大多数人在做数学时并不凭直觉期望会发生这种情况。在某些情况下,例如-1 + 1,它是无害的;但是对于1024^4,它可能会以完全出乎意料的结果使人们措手不及,这与他们期望看到的结果相去甚远。我认为至少应该给用户一个警告或说明,而不是默默地忽略它。
    • @iowatiger08:int的大小是固定的;它确实不依赖于JVM。 Java 不是 C。
    【解决方案4】:

    我不知道为什么第二个变种不会产生错误。

    您建议的行为 - 即,当计算产生的值大于可以存储在整数中的最大值时产生诊断消息 - 是 功能。要使用任何功能,必须考虑该功能,将其视为一个好主意、设计、指定、实施、测试、记录并交付给用户。

    对于 Java,该列表中的一件或多件事情没有发生,因此您没有该功能。我不知道是哪一个;您必须咨询 Java 设计师。

    对于 C#,所有这些事情确实发生了 - 大约 14 年前的现在 - 所以 C# 中的相应程序从 C# 1.0 开始就产生了错误。

    【讨论】:

    • 这不会增加任何有用的东西。虽然我不介意攻击 Java,但它根本没有回答 OP 的问题。
    • @Seiyria:原发帖人在问“为什么不呢?”问题——“为什么世界不是我想的那样?”不是关于实际代码的精确技术问题,因此这对于 StackOverflow 来说是个坏问题。对一个模糊和非技术性问题的正确答案是模糊和非技术性的这一事实应该不足为奇。我鼓励原始发帖人提出更好的问题,避免“为什么不呢?”问题。
    • @Seiyria:我注意到的公认答案也没有回答这个模糊和非技术性的问题;问题是“为什么这不是错误?”并且接受的答案是“因为它是合法的”。这只是重申问题;回答“为什么天空不是绿色的?”用“因为它是蓝色的”不能回答这个问题。但是由于这个问题是一个糟糕的问题,我一点也不怪回答者;答案是对一个糟糕问题的完全合理的回答。
    • 先生。埃里克,这是我发布的问题:“为什么 int i = 1024 * 1024 * 1024 * 1024;在 eclipse 中没有错误报告?”。而 arshajii 的答案正是我想要的(也许更多)。有时我无法以非常准确的方式表达任何问题。我认为这就是为什么有些人会在 Stackoverflow 中更准确地修改一些已发布的问题。我想如果我想“因为它是合法的”得到答案,我不会发布这个问题。我会尽力发布一些“常规问题”,但请理解像我这样的学生,不是那么专业。谢谢。
    • @WUJ 这个答案恕我直言,提供了额外的洞察力和观点。在阅读了所有答案后,我发现这个答案与提供的其他答案一样有效。它还让人们意识到开发人员并不是某些软件产品的唯一实施者。
    【解决方案5】:

    除了 arshajii 的回答,我还想展示一件事:

    导致错误的不是 assignment,而只是 literal 的使用。 当你尝试

    long i = 2147483648;
    

    您会注意到它还会导致编译错误,因为右侧仍然是 int-literal 并且超出范围。

    所以使用int-values(包括赋值)的操作可能会溢出而没有编译错误(也没有运行时错误),但编译器无法处理那些太大的文字。

    【讨论】:

    • 对。将 int 分配给 long 包括隐式强制转换。但是该值永远不能作为 int 存在,首先要被强制转换:)
    猜你喜欢
    • 2014-09-20
    • 1970-01-01
    • 2013-12-28
    • 2021-03-24
    • 2015-02-16
    • 2020-05-06
    • 2012-08-15
    • 2012-09-18
    • 1970-01-01
    相关资源
    最近更新 更多