首页 文章

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

提问于
浏览 1890
484

我有以下代码:

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 作为值?

--update

这是字节码:

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

}

我会读到关于instructions以试图理解......

27 回答

  • 350
    • 前缀表示法将在计算表达式之前增加变量BEF .

    • 后缀表示法将在表达式评估后递增 .

    但是“ = " has a lower operator precedence than " ++ ” .

    所以 x=x++; 应评估如下

    • x 准备分配(已评估)

    • x 递增

    • x 的前一个值已分配给 x .

  • 168

    x = x++ 以下列方式工作:

    • 首先它评估表达式 x++ . 对此表达式的求值会生成一个表达式值(在递增之前为 x 的值)并递增 x .

    • 稍后它将表达式值赋给 x ,覆盖递增的值 .

    因此,事件序列如下所示(它是一个实际的反编译字节码,由 javap -c 产生,带有我的注释):

    8:   iload_1         // Remember current value of x in the stack
       9:   iinc    1, 1    // Increment x (doesn't change the stack)
       12:  istore_1        // Write remebered value from the stack to x
    

    为了比较, x = ++x

    8:   iinc    1, 1    // Increment x
       11:  iload_1         // Push value of x onto stack
       12:  istore_1        // Pop value from the stack to x
    
  • 104

    您真的不需要机器代码来了解正在发生的事情 .

    根据定义:

    • 赋值运算符计算右侧表达式,并将其存储在临时变量中 .

    1.1 . x的当前值被复制到此临时变量中

    1.2 . x现在递增 .

    • 然后将临时变量复制到表达式的左侧,这是偶然的x!这就是x的旧值再次复制到自身的原因 .

    这很简单 .

  • 52

    增量运算符应用于您指定的同一变量 . 那是在惹麻烦 . 我确信你可以在运行这个程序时看到你的x变量的值....这应该清楚为什么循环永远不会结束 .

  • 34

    Note :最初我在此答案中发布了C#代码用于说明目的,因为C#允许您通过 ref 关键字引用传递 int 参数 . 我决定使用我在Google上找到的第一个MutableInt类来使用实际的合法Java代码更新它,以便对C#中的 ref 进行排序 . 我可以完成所有Java开发工作;所以我知道可能有更多的惯用方法来说明这一点 .


    也许如果我们写出一个方法来做相当于 x++ 的工作,它会使这个更清楚 .

    public MutableInt postIncrement(MutableInt x) {
        int valueBeforeIncrement = x.intValue();
        x.add(1);
        return new MutableInt(valueBeforeIncrement);
    }
    

    对?增加传递的值并返回原始值:这是postincrement运算符的定义 .

    现在,让我们看看您的示例代码中如何显示此行为:

    MutableInt x = new MutableInt();
    x = postIncrement(x);
    

    postIncrement(x) 做什么?增加 x ,是的 . 然后 returns what x was before the increment . 然后将此返回值分配给 x .

    因此,分配给 x 的值的顺序为0,然后是1,然后是0 .

    如果我们重写以上内容,这可能会更清楚:

    MutableInt x = new MutableInt();    // x is 0.
    MutableInt temp = postIncrement(x); // Now x is 1, and temp is 0.
    x = temp;                           // Now x is 0 again.
    

    你对 y ,"you can see that it first increments x, and later attributes it to y"替换上述作业左侧的 x 这一事实感到困惑 . 它不是 x 被分配给 y ;它是 the value formerly assigned to x . 真的,注入 y 使得事情与上面的场景没有什么不同;我们只是得到:

    MutableInt x = new MutableInt();    // x is 0.
    MutableInt y = new MutableInt();    // y is 0.
    MutableInt temp = postIncrement(x); // Now x is 1, and temp is 0.
    y = temp;                           // y is still 0.
    

    所以很明显: x = x++ 实际上不会改变x的值 . 它总是使x的值为x0,然后是x0 1,然后是x0 .


    Update :顺便说一下,为了避免怀疑 x 曾被分配给1 "between"增量操作和上面例子中的赋值,我把一个快速演示放在一起来说明这个中间值确实"exist,"虽然它永远不会是"seen"执行线程 .

    演示在循环中调用 x = x++; ,而单独的线程连续将 x 的值打印到控制台 .

    public class Main {
        public static volatile int x = 0;
    
        public static void main(String[] args) {
            LoopingThread t = new LoopingThread();
            System.out.println("Starting background thread...");
            t.start();
    
            while (true) {
                x = x++;
            }
        }
    }
    
    class LoopingThread extends Thread {
        public @Override void run() {
            while (true) {
                System.out.println(Main.x);
            }
        }
    }
    

    以下是上述程序输出的摘录 . 注意1和0的不规则出现 .

    Starting background thread...
    0
    0
    1
    1
    0
    0
    0
    0
    0
    0
    0
    0
    0
    0
    1
    0
    1
    
  • 29

    没有任何答案在哪里,所以这里是:

    当你写 int x = x++ 时,你并没有指定 x 本身处于新值,你是将 x 指定为 x++ 表达式的返回值 . 这恰好是 x 的原始值,如Colin Cochrane's answer中暗示的那样 .

    为了好玩,请测试以下代码:

    public class Autoincrement {
            public static void main(String[] args) {
                    int x = 0;
                    System.out.println(x++);
                    System.out.println(x);
            }
    }
    

    结果将是

    0
    1
    

    表达式的返回值是 x 的初始值,为零 . 但是稍后,当读取 x 的值时,我们会收到更新的值,即一个 .

  • 18

    答案很简单 . 它与评估事物的顺序有关 . x++ 返回值 x 然后递增 x .

    因此,表达式 x++ 的值为 0 . 所以你每次都在循环中分配 x=0 . 当然 x++ 会递增此值,但这会在赋值之前发生 .

  • 10
    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; 相同 .

  • 8

    在将值递增1之前,将值分配给变量 .

  • 7

    句子

    x = x++;
    

    “翻译”为

    x = x;
    x = x + 1;
    

    而已 .

  • 7
    x = x++; (increment is overriden by = )
    

    因为上述陈述x永远不会达到3;

  • 5

    我想知道Java规范中是否有任何内容可以准确定义它的行为 . (该陈述的明显含义是我懒得检查 . )

    从Tom的字节码注意到,关键行是7,8和11.第7行将x加载到计算堆栈中 . 第8行增加x . 第11行将堆栈中的值存储回x . 在正常情况下,您没有将值分配给自己,我认为没有任何理由您无法加载,存储,然后递增 . 你会得到相同的结果 .

    比如,假设你有一个更正常的例子,你写了类似的东西:z =(x)(y);

    是否说(伪代码跳过技术细节)

    load x
    increment x
    add y
    increment y
    store x+y to z
    

    要么

    load x
    add y
    store x+y to z
    increment x
    increment y
    

    应该是无关紧要的 . 我想,要么实施都应该有效 .

    在编写依赖于此行为的代码时,我会非常谨慎 . 它看起来非常依赖于实现,在我之间的规格之间 . 它唯一会产生影响的是你做了一些疯狂的事情,比如这里的例子,或者你有两个线程在运行并依赖于表达式中的评估顺序 .

  • 3

    您有效地获得了以下行为 .

    • 抓取x的值(即0)为右侧的"the result"

    • 递增x的值(所以x现在为1)

    • 将右侧的结果(保存为0)分配给x(x现在为0)

    这个想法是后增量运算符(x)增加有问题的变量AFTER返回它的值以便在它使用的等式中使用 .

    编辑:由于评论而添加一点点 . 考虑如下 .

    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

    它已经被其他人很好地解释了 . 我只是包含相关Java规范部分的链接 .

    x = x是一个表达式 . Java将遵循evaluation order . 它将首先评估表达式x,其中will increment x and set result value to the previous value of x . 然后它将assign the expression result变为x . 最后,x返回其先前的值 .

  • 1

    这是因为在这种情况下它永远不会增加 . x++ 将在递增之前首先使用它的值,就像这种情况一样,它将像:

    x = 0;
    

    但如果你做 ++x; ,这会增加 .

  • 1

    将x视为一个函数调用"returns"在增量之前是什么X('s why it' s称为后增量) .

    所以操作顺序是:
    1:在递增之前缓存x的值
    2:增量x
    3:返回缓存值(x在增加之前)
    4:返回值分配给x

  • 1

    就我所见,由于赋值覆盖递增的值,错误发生在增量之前的值,即它撤消增量 .

    具体地,“x”表达式在递增之前具有值“x”而不是在递增之后具有值“x”的“x” .

    如果您有兴趣研究字节码,我们将看看有问题的三行:

    7:   iload_1
     8:   iinc    1, 1
    11:  istore_1
    

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

    这就是为什么上面的代码将无限循环,而带x的版本则不会 . x的字节码应该看起来完全不同,据我记得一年多前写的1.3 Java编译器,字节码应该是这样的:

    iinc 1,1
    iload_1
    istore_1
    

    因此,只需交换两个第一行,就会改变语义,使得在增量(即表达式的“值”)之后,堆栈顶部留下的值是增量之后的值 .

  • 1

    它正在发生,因为它的帖子增加了 . 这意味着在计算表达式后变量会递增 .

    int x = 9;
    int y = x++;
    

    x现在是10,但是y是9,x之前的值是递增的 .

    Definition of Post Increment中查看更多信息 .

  • 1

    检查以下代码,

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

    输出将是,

    temp = 0
    x = 0
    

    post increment 表示 increment the value and return the value before the increment . 这就是为什么值 temp0 . 那么如果 temp = i 并且这是一个循环(第一行代码除外)该怎么办呢 . 就像在问题!!!!

  • 0

    发生这种情况是因为 x 的值根本没有增加 .

    x = x++;
    

    相当于

    int temp = x;
    x++;
    x = temp;
    

    说明:

    我们来看看这个操作的字节代码 . 考虑一个示例类:

    class test {
        public static void main(String[] args) {
            int i=0;
            i=i++;
        }
    }
    

    现在运行类反汇编程序,我们得到:

    $ javap -c test
    Compiled from "test.java"
    class test extends java.lang.Object{
    test();
      Code:
       0:    aload_0
       1:    invokespecial    #1; //Method java/lang/Object."<init>":()V
       4:    return
    
    public static void main(java.lang.String[]);
      Code:
       0:    iconst_0
       1:    istore_1
       2:    iload_1
       3:    iinc    1, 1
       6:    istore_1
       7:    return
    }
    

    现在Java VM是基于堆栈的,这意味着对于每个操作,数据将被压入堆栈并从堆栈中弹出数据以执行操作 . 还有另一种数据结构,通常是用于存储局部变量的数组 . 局部变量给出id,它们只是数组的索引 .

    让我们看一下mnemonics中的mnemonics方法:

    • iconst_0 :常量值 0 被推入堆栈 .

    • istore_1 :弹出堆栈的顶部元素,并将其存储在索引 1 的局部变量中
      这是 x .

    • iload_1 :位于 1x 的值 0 的值被推入堆栈 .

    • iinc 1, 1 :内存位置 1 的值增加 1 . 所以 x 现在变成 1 .

    • istore_1 :堆栈顶部的值存储在内存位置 1 . 即 0 被赋予 x overwriting 其递增值 .

    因此 x 的值不会改变,从而导致无限循环 .

  • 0

    这个说法:

    x = x++;
    

    评估如下:

    • x 推入堆栈;

    • 增量 x ;

    • 从堆栈中弹出 x .

    所以 Value 没有变化 . 比较一下:

    x = ++x;
    

    评估为:

    • 增量 x ;

    • x 推入堆栈;

    • 从堆栈中弹出 x .

    你想要的是:

    while (x < 3) {
      x++;
      System.out.println(x);
    }
    
  • 0

    来自http://download.oracle.com/javase/tutorial/java/nutsandbolts/op1.html

    可以在操作数之前(前缀)或之后(后缀)应用递增/递减运算符 . 代码结果;和结果;将两个结果都加1 . 唯一的区别是前缀版本(结果)评估为增量值,而后缀版本(结果)评估为原始值 . 如果您只是执行简单的增量/减量,那么选择哪个版本并不重要 . 但是,如果您在较大的表达式中使用此运算符,则您选择的运算符可能会产生显着差异 .

    为了说明,请尝试以下方法:

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

    其中将打印1和0 .

  • 0

    这符合您对另一个人的期望 . 这是前缀和后缀之间的区别 .

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

    如果在rhs上,则在数字递增之前返回结果 . 改为x,一切都会好的 . Java会对此进行优化以执行单个操作(将x分配给x)而不是增量 .

  • 0

    我认为因为Java中的优先级高于=(赋值)...是吗?看看http://www.cs.uwf.edu/~eelsheik/cop2253/resources/op_precedence.html ......

    如果你写x = x 1 ...的方式相同,那么优先级高于=(赋值)

  • 0

    x++ 表达式的计算结果为 x . ++ 部分影响评估后的值,而不是语句后的值 . 所以 x = x++ 被有效地翻译成了

    int y = x; // evaluation
    x = x + 1; // increment part
    x = y; // assignment
    
  • -1

    该值保持为0,因为 x++ 的值为0.在这种情况下,如果 x 的值增加与否则无关紧要,则执行赋值 x=0 . 这将覆盖临时递增的 x 值(对于"very short time"为1) .

相关问题