为什么int i = 1024 * 1024 * 1024 * 1024编译时没有错误?


152

上限为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中的一个基本问题,但是我不知道为什么第二个变体不会产生任何错误。


10
即使编译器通常将计算作为优化优化“折叠”成单个值,但如果结果是溢出,它也不会这样做,因为没有优化可以改变程序的行为。
热门点击2014年

1
而且它无法解释2147483648:这个字面意义不大
DenysSéguret2014年

1
而且Java不会报告整数溢出-操作会“静默”失败。
热门点击2014年

5
@JacobKrall:无论是否启用检查功能,C#都将其报告为缺陷。除非处于未检查区域内,否则所有仅由常量表达式组成的计算都将自动检查。
埃里克·利珀特

54
我不鼓励您在StackOverflow上问“为什么不”的问题;他们很难回答。一个“为什么不”的问题预设为,世界显然应该是一种不应该这样的方式,并且必须有充分的理由使它那样做。这个假设几乎是无效的。一个更精确的问题将是“规范的哪个部分描述了如何计算常量整数算术?”之类的问题。或“如何用Java处理整数溢出?”
埃里克·利珀特

Answers:


233

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

导致错误而不是赋值的是超出范围的文字

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

相反,long文字可以很好地编译:

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

请注意,实际上,结果仍然在编译时计算的,因为它1024 * 1024 * 1024 * 1024是一个常量表达式

int i = 1024 * 1024 * 1024 * 1024;

变成:

   0: iconst_0      
   1: istore_1      

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


JLS§3.10.1起(感谢@ChrisK在评论中提出):

如果type的十进制文字int大于2147483648(2 31),或者十进制文字2147483648出现在非一元减运算符的操作数之外的任何地方,则这是编译时错误(§15.15.4)。


12
对于乘法,JLS说:如果整数乘法溢出,则结果是数学乘积的低阶位,以某种足够大的二进制补码格式表示。结果,如果发生溢出,则结果的符号可能与两个操作数值的数学乘积的符号不同。
克里斯K

3
极好的答案。有些人似乎给人以溢出是某种错误或失败的印象,但事实并非如此。
Wouter Lievens 2014年

3
@ iowatiger08语言语义由JLS概述,它独立于JVM(因此,使用哪个JVM都无关紧要)。
arshajii 2014年

4
@WouterLievens,溢出正常的“不寻常”的条件,如果不彻底错误条件。这是有限精度数学的结果,大多数人在做数学时都不凭直觉期望发生这种情况。在某些情况下,例如-1 + 1,它是无害的;但是1024^4这样做可能会给盲目的人带来完全出乎意料的结果,远远超出他们的预期。我认为应该至少向用户发出警告或提示,而不是无声地忽略它。
Phil Perry

1
@ iowatiger08:int的大小是固定的;它依赖于JVM。Java是不是 C.
马丁·施罗德

43

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

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

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

考虑以下代码示例:

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

您希望这会产生编译错误吗?现在变得更滑了。
如果我们将一个包含3次迭代的循环并乘以该循环怎么办?

允许编译器进行优化,但不能在执行过程中更改程序的行为。


有关如何实际处理此案的一些信息:

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

其他语言或API使用动态位数(BigInteger在Java中),引发异常或将值设置为不可取的值(例如,非数字)。


8
对我来说,您的陈述“ 2147483648虽然2147483648L还没有一个值(虽然是)” 确实巩固了@arshajii试图提出的观点。
kdbanman 2014年

啊,对不起,是的,那是我。我在您的答案中缺少概念溢出/模块化算术。请注意,如果您不同意我的修改,可以回滚。
Maarten Bodewes 2014年

@owlstead您的编辑实际上是正确的。我之所以不包括它,是因为:无论如何1024 * 1024 * 1024 * 1024处理,我都非常想强调一点,这与写作并不相同2147473648。一种语言可以通过多种方式(您已经列出了几种)来处理它。它是合理分离的,并且很有用。所以我会离开。当您对一个热门问题有较高的排名时,许多信息就变得越来越必要。
Cruncher 2014年

16

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

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

对于Java,该列表中的一项或多项未发生,因此您没有该功能。我不知道哪一个;您必须要请Java设计师。

对于C#,所有这些事情都发生了-大约在14年前-如此,因此C#中相应的程序自C#1.0起产生了错误。


45
这不会增加任何帮助。虽然我不介意Java,但它根本没有回答OP的问题。
Seiyria 2014年

29
@Seiyria:原始海报问“为什么不?” 问题-“为什么世界不是我认为的样子?” 这不是关于实际代码精确技术问题,因此对于StackOverflow来说是一个坏问题。对模糊和非技术性问题的正确答案既模糊又非技术性的事实应该不足为奇。我鼓励原始海报提出一个更好的问题,并避免使用“为什么不这样做”?问题。
埃里克·利珀特

18
@Seiyria:我注意到的可接受的答案也没有回答这个模糊且非技术性的问题;问题是“为什么这不是错误?” 并接受的答案是“因为它是合法的”。这只是在重提问题 ; 回答“为什么天空不是绿色?” 带有“因为它是蓝色”的字词无法回答问题。但是由于这个问题是一个不好的问题,我完全不怪答复者。答案是对一个糟糕问题的完全合理的答案。
埃里克·利珀特

13
埃里克先生,这是我发布的问题:“为什么int i = 1024 * 1024 * 1024 * 1024;在日食中没有错误报告?”。而arshajii的答案正是我所想要的(可能更多)。有时我无法以非常准确的方式表达任何问题。我认为这就是为什么有些人在Stackoverflow中更准确地修改了一些发布的问题。我认为,如果我想得到“因为这是合法的”答案,就不会发布此问题。我会尽力发布一些“常规问题”,但是请理解像我这样的学生和专业人士。谢谢。
WUJ

5
@WUJ这个答案恕我直言提供了更多的见解和观点。阅读所有答案后,我发现此答案与提供的其他答案一样有效。同时,它也使人们意识到开发人员不是某些软件产品的唯一实现者。
SoftwareCarpenter

12

除了arshajii的答案,我还要显示另外一件事:

导致错误的不是赋值,而是文字的使用。当你尝试

long i = 2147483648;

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

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


1
对。将int分配给long包括隐式强制转换。但是该值永远不会作为int的第一位出现:)
Cruncher 2014年

4

答:因为这不是错误。

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

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

您可以肯定地争论这种行为是否有帮助,以及编译器是否应向您发出警告。我个人会说警告会很有用,但是错误将是不正确的,因为它是合法的Java。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.