为什么这个程序会进入无限循环?

509

我有以下的代码:

public class Tests {
    public static void main(String[] args) throws Exception {
        int x = 0;
        while(x<3) {
            x = x++;
            System.out.println(x);
        }
    }
}
我们知道应该只写x++x=x+1,但在x = x++中,它首先将x赋值给自己,然后再将其递增。为什么x的值仍然是0--更新 这是字节码:
public class Tests extends java.lang.Object{
public Tests();
  Code:
   0:   aload_0
   1:   invokespecial   #1; //Method java/lang/Object."<init>":()V
   4:   return

public static void main(java.lang.String[])   throws java.lang.Exception;
  Code:
   0:   iconst_0
   1:   istore_1
   2:   iload_1
   3:   iconst_3
   4:   if_icmpge   22
   7:   iload_1
   8:   iinc    1, 1
   11:  istore_1
   12:  getstatic   #2; //Field java/lang/System.out:Ljava/io/PrintStream;
   15:  iload_1
   16:  invokevirtual   #3; //Method java/io/PrintStream.println:(I)V
   19:  goto    2
   22:  return

}

我会阅读有关指令的内容,以尝试理解...


8
我猜测正在发生的事情是:1.将x加载到寄存器中(=0); 2.将x增加(x=1); 3.将寄存器值保存到x中(x=0)。在C/C++中,这将导致未定义的行为,因为没有正式的序列点来定义2和3的顺序。希望有人能引用Java规范中与此相等的内容。 - Rup
19
我们尝试在 C++ 中运行这个程序,结果打印出了 1、2、3 并退出了。我没想到会这样。我猜这是编译器相关的,因为这是未定义的行为。我们使用的是 GNU 的 g++ 编译器。 - grieve
14
@saj,x++是后自增;x=结果的赋值;x++的结果是原始的x(虽然有一个增量的副作用,但这不会改变结果),因此可以解释为var tmp = x; x++; x = tmp; - Marc Gravell
5
既然我有一个受欢迎的问题,我感到后悔了。即使选择了正确答案,重复的回答仍不停地涌现。我的“最近活动”页面满是相同的答案,而且还在继续增加... - The Student
3
@Rob Vermeulen,在发表评论之前,您可能需要阅读完整的问题.. ;) 这是我的一个学生编写的代码,我想知道这种行为的原因。 - The Student
显示剩余18条评论
26个回答

7
您实际上获得了以下行为。
1. 获取x的值(即0)作为右侧“结果”。 2. 增加x的值(所以x现在是1)。 3. 将右侧的结果(保存为0)分配给x(x现在是0)。
其思想是,后缀递增运算符(x ++)会在返回其用于方程式中的值之后将其递增。
编辑:由于评论的原因,添加了一点内容。可以将其视为以下内容。
x = 1;        // x == 1
x = x++ * 5;
              // First, the right hand side of the equation is evaluated.
  ==>  x = 1 * 5;    
              // x == 2 at this point, as it "gave" the equation its value of 1
              // and then gets incremented by 1 to 2.
  ==>  x = 5;
              // And then that RightHandSide value is assigned to 
              // the LeftHandSide variable, leaving x with the value of 5.

1
好的,但是步骤2和3的顺序是由什么确定的? - Rup
1
@Rup - 语言定义了它。等式的右侧首先被评估(在这种情况下是 "x++"),然后将结果分配给左侧的变量。这就是语言的工作方式。至于 "x++" "返回" 等式的 x,那就是后缀递增运算符的工作原理(返回 x 的值,然后将其递增)。如果是 "--x",那么它将是(递增 x,然后返回值)。这里不是正确的单词,但你明白我的意思。 - RHSeeger

5
这是因为在这种情况下,它从未被递增。 x ++ 将先使用它的值,然后再递增,就像在此示例中一样:
x = 0;

但是如果你执行++x;,这将会增加。


1
如果您进行测试,可以看到它首先进行递增,然后进行属性分配。因此,它不应该被归零。 - The Student
2
@Tom:看我的回答——我在一个测试中展示了 x++ 实际上返回的是 x 的旧值。这就是出错的地方。 - Robert Munteanu
1
如果你做一个测试——某些人似乎认为用C语言编写的测试可以告诉我们Java会做什么,但它甚至无法告诉我们C语言会做什么。 - Jim Balter

3

这个值保持为0是因为 x++ 的值是0。在这种情况下,不管x的值是否增加,赋值 x=0 都会执行。这将覆盖临时增加的x的值(它只是“短暂”为1)。


1
但是x++是后置操作。因此,在赋值完成后,x必须递增。 - Sagar V
2
@Sagar V:仅适用于表达式x++,而不是整个赋值语句x=x++; - Progman
1
不,我认为只需要在读取赋值中使用的 x 的值之后将其递增。 - Rup

1

这个代码的运行方式符合你对另一个代码的期望。这是前缀和后缀的区别。

int x = 0; 
while (x < 3)    x = (++x);

1
据我所见,错误是由于赋值覆盖了递增后的值,而使用递增前的值,即撤销了递增操作。
具体来说,“x++”表达式的值是递增前的'x'值,而“++x”则是递增后的'x'值。
如果您有兴趣调查字节码,我们将查看三行代码:
 7:   iload_1
 8:   iinc    1, 1
11:  istore_1

7: iload_1 # 将第2个本地变量的值放到堆栈上
8: iinc 1,1 # 将第2个本地变量增加1,注意它不会影响堆栈!
9: istore_1 # 将堆栈顶部的元素弹出并将该元素的值保存到第2个本地变量中
(您可以在此处阅读每个JVM指令的效果 here)

这就是为什么上面的代码会无限循环,而使用++x的版本则不会。 对于++x的字节码应该看起来截然不同,据我记得一年多前所写的1.3 Java编译器,其字节码应该如下:

iinc 1,1
iload_1
istore_1

因此,只需交换前两行即可改变语义,以便在增量后,堆栈顶部留下的值(即表达式的“值”)是增量后的值。


1
    x++
=: (x = x + 1) - 1

所以:

   x = x++;
=> x = ((x = x + 1) - 1)
=> x = ((x + 1) - 1)
=> x = x; // Doesn't modify x!

然而

   ++x
=: x = x + 1

所以:

   x = ++x;
=> x = (x = x + 1)
=> x = x + 1; // Increments x

当然,最终结果与单独使用x++;++x;的效果相同。


1

将x++视为一个函数调用,它“返回”增量之前的X值(这就是为什么它被称为后增量)。

因此,操作顺序如下:
1:缓存增量之前的x值
2:增加x
3:返回缓存的值(增量之前的x值)
4:返回值分配给x


1
好的,但是步骤3和4的顺序是由什么指定的? - Rup
1
“返回递增前的X值”是错误的,请查看我的更新。 - The Student
1
实际上,步骤3和4并不是独立的操作 - 它不是真正的函数调用返回一个值,只是帮助我们以这种方式思考。每当你有一个赋值操作,右边的表达式会被“评估”,然后结果被赋给左边,这个评估结果可以被看作是一个返回值,因为它帮助你理解操作的顺序,但实际上并不是。 - jhabbott
1
哎呀,对了。我指的是步骤2和4——为什么返回值会覆盖增加后的值? - Rup
2
这是赋值操作的定义的一部分,首先对右侧进行完全评估,然后将结果分配给左侧。 - jhabbott
@jhabbot。几乎正确,从技术上讲,在表达式求值之后发生x++(想象一下(x=a++ + b++)),但在赋值之前,因为++具有更高的运算符优先级。 - Jaydee

1

当++在右侧时,结果会在数字递增之前返回。 将其改为++x,就不会出现问题了。 Java会优化此操作以执行单个操作(将x分配给x),而不是递增。


0

请检查以下代码:

    int x=0;
    int temp=x++;
    System.out.println("temp = "+temp);
    x = temp;
    System.out.println("x = "+x);

输出将会是:

temp = 0
x = 0

后置递增 意味着先返回值再将其加一。这就是为什么 temp 的值是 0。那么如果 temp = i,并且这是在一个循环中(除了第一行代码),会怎样呢?就像问题描述的那样!!!


0
 x = x++; (increment is overriden by = )

由于上述语句,x永远不会达到3;


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接