Почему это входит в бесконечную петлю?

у меня есть следующий код:

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

}

Я прочитаю о - инструкции попытаться понять...

27 ответов


Примечание: Первоначально я разместил код C# в этом ответе для иллюстрации, так как C# позволяет вам пройти int параметры по ссылке с ref ключевое слово. Я решил обновить его с помощью фактического юридического кода Java, используя первый MutableInt класс я нашла на Google для сортировки приближенных, что ref в C#. Я не могу сказать, помогает или вредит ответ. Я скажу, что я лично не сделал все, что много разработки Java; так что насколько я знаю, могут быть гораздо более идиоматические способы проиллюстрировать этот момент.


возможно, если мы напишем метод, чтобы сделать то же самое, что x++ это сделает это более ясным.

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

верно? Приращение значения прошел и вернуть исходное значение: это определение оператора постинкремент.

теперь давайте посмотрим, как это поведение играет в вашем примере кода:

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

postIncrement(x) что делает? Шагом x да. А потом ... --27-->возвращает x был до инкремента. Затем это возвращаемое значение присваивается 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.

ваша фиксация на том, что при замене x в левой части приведенного выше задания с y "вы видите, что это сначала увеличивает x, а затем приписывает его y" поражает меня как запутанный. Это не x, который присваивается y; это значение ранее назначенное на 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 снова.


обновление: кстати, дабы вы не сомневались в этом x когда-либо присваивается 1 "между" операцией приращения и назначением в приведенном выше примере, я собрал быструю демонстрацию, чтобы проиллюстрировать, что это промежуточное значение действительно "существует", хотя оно никогда не будет "видно" в исполняющем потоке.

демо-звонки 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);
        }
    }
}

Ниже приведена выдержка из приведенного выше вывода программы. Обратите внимание на нерегулярное появление как 1s, так и 0s.

Starting background thread...
0
0
1
1
0
0
0
0
0
0
0
0
0
0
1
0
1

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

это происходит потому, что значение 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 основанный стог который середины для каждой деятельности, данные будут нажаты на стог и из стека появятся данные для выполнения операции. Существует также другая структура данных, обычно массив для хранения локальных переменных. Локальные переменные задаются идентификаторами, которые являются только индексами массива.

давайте посмотрим на мнемоника на main() способ:

  • iconst_0: значение константы 0 нажимается на стопку.
  • istore_1: верхний элемент стека извлекается и хранится в этот локальная переменная с индексом 1
    что is x.
  • iload_1 : значение в местоположение 1 это значение x которая составляет 0, проталкивается в стек.
  • iinc 1, 1 : значение в расположение памяти 1 увеличивается на 1. Так что x теперь становится 1.
  • istore_1 : значение в верхней части стек хранится в памяти1. Это 0 присваивается к x перезапись ее увеличенное значение.

отсюда значение x не изменяется, что приводит к бесконечному циклу.


  1. Префиксная нотация увеличит переменную перед вычислением выражения.
  2. Постфиксная нотация будет увеличиваться после оценки выражения.
"=" имеет более низкий приоритет операторов, чем "++".

так x=x++; должны оценить следующим образом

  1. x подготовлено для назначения (оценка)
  2. x incremented
  3. Предыдущее значение x назначенные к x.

ни один из ответов, где совсем пятно, так что здесь идет:

когда вы пишите int x = x++, вы не назначаете x для себя новое значение, вы присваиваете x быть возвращаемым значением x++ выражение. Что является исходным значением x, как намекнул в ответ Колина Кокрейна .

для удовольствия проверьте следующий код:

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

результат будет

0
1

возвращение значением выражения является исходное значение x, который равен нулю. Но позже, при чтении значения x, мы получаем обновленное значение, то есть один.


Это уже было хорошо объяснено другими. Я просто включаю ссылки на соответствующие разделы спецификации Java.

x = x++ - это выражение. Java будет следовать порядок оценки. Сначала он будет оценивать выражение x++, которое увеличит x и установит значение результата на Предыдущее значение x. Тогда это будет присвоить результат выражения к переменной x. В конце x возвращается к своему предыдущему значению.


такое заявление:

x = x++;

оценивает такой:

  1. пуш x в стек;
  2. инкремент x;
  3. поп x из стека.

таким образом, значение не изменяется. Сравните это с:

x = ++x;

, которая оценивается как:

  1. инкремент x;
  2. пуш x в стек;
  3. поп x от стек.

ты хочешь:

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

ответ довольно прост. Это связано с порядком, в котором вещи оцениваются. x++ возвращает значение x затем с шагом x.

следовательно, значение выражения x++ is 0. Таким образом, вы назначаете x=0 каждый раз в цикле. Конечно x++ инкрементирует это значение, но это происходит до назначения.


от http://download.oracle.com/javase/tutorial/java/nutsandbolts/op1.html

операторы инкремента/декремента могут применяется до (префикс) или после (постфикс) операнд. Код результат++; и результат++; оба закончатся в результате увеличивается на единицу. Разница лишь в том, что префикс версия (результат++) оценивает увеличенное значение,а версия postfix (result++) оценивает к исходное значение. Если ты ... просто выполнение простого инкремент / декремент, на самом деле это не так важно, какую версию вы выберете. Но при использовании этого оператора в части большее выражение, то, которое вы выберите может сделать значительно разница.

чтобы проиллюстрировать, попробуйте следующее:

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

который будет печатать 1 и 0.


вы эффективно получаете следующее поведение.

  1. возьмите значение x (которое равно 0) как "результат" правой стороны
  2. инкремент значение x (так что x теперь 1)
  3. назначьте результат правой стороны (который был сохранен как 0) x (x теперь 0)

идея заключается в том, что оператор post-increment (x++) увеличивает эту переменную после возврата ее значения для использования в уравнении, которое она используется в.

Edit: добавление небольшого бита из-за комментария. Рассмотрим это следующим образом.

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. оператор присваивания вычисляет правое выражение и сохраняет его во временной переменной.

    1.1. Текущее значение x копируется в эту временную переменную

    1.2. x теперь увеличивается.

  2. временная переменная затем копируется в левую часть выражение, которое случайно x! Так вот почему старое значение x снова копируется в себя.

Это довольно просто.


Это потому, что он никогда не увеличивается в этом случае. x++ сначала будет использовать его значение, прежде чем увеличивать, как в этом случае, это будет похоже:

x = 0;

но если вы это сделаете ++x; это позволит увеличить.


значение остается на 0, потому что значение x++ равен 0. В этом случае не имеет значения, если значение x увеличивается или нет, задание x=0 выполняется. Это перезапишет временное увеличенное значение x (что было 1 за "очень короткое время").


это работает так, как вы ожидаете от другого. Это разница между префиксом и постфиксом.

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

подумайте о x++ как вызове функции, который "возвращает" то, что X было до инкремент (вот почему он называется пост-инкрементом).

таким образом, порядок операции:
1: кэшировать значение x перед увеличением
2: приращение x
3. возвращает кэшированное значение (х, прежде чем он был увеличен)
4: возвращаемое значение присваивается x


когда ++ находится на rhs, результат возвращается до увеличения числа. Изменение ++X и все было бы хорошо. Java оптимизировал бы это для выполнения одной операции (назначение x к x), а не приращения.


насколько я вижу, ошибка возникает из-за того, что присвоение переопределяет увеличенное значение со значением до увеличения, т. е. оно отменяет приращение.

в частности, выражение" 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 здесь)

вот почему приведенный выше код будет цикл бесконечно, в то время как версия с ++X не будет. Байткод для ++Х должно выглядеть совсем иначе, насколько я помню из компилятора 1.3 Java, который я написал чуть больше года назад, байт-код должен идти примерно так:

iinc 1,1
iload_1
istore_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; на линии сама по себе.


наказание

x = x++;

"переводит" к

x = x;
x = x + 1;

вот именно.


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

из-за вышеуказанного утверждения x никогда не достигает 3;


интересно, есть ли что-нибудь в спецификации Java, которое точно определяет поведение этого. (Очевидно, что это утверждение подразумевает, что я слишком ленив, чтобы проверить.)

Примечание из байт-кода тома, ключевые строки 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

не имеет значения. Я думаю, что любая реализация должна быть действительной.

Я был бы очень осторожен в написании кода, который зависит от этого поведения. Это очень выглядит реализация зависит от меня, между трещинами в спецификациях. Единственное время, когда это имело бы значение, - это если бы вы сделали что-то сумасшедшее, как пример здесь, или если бы у вас было два потока и зависели от порядка оценки в выражении.


Я думаю, потому что в Java ++ имеет более высокий приоритет, чем = (цессии)...Не так ли? Посмотри на http://www.cs.uwf.edu/~eelsheik/cop2253/resources/op_precedence.html...

то же самое, если вы пишете x=x+1...+ имеет более высокий приоритет, чем = (цессии)


на x++ выражение x. The ++ часть влияет на значение после оценка, а не после сообщении. так что x = x++ эффективно переводится в

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

перед увеличением значения на единицу значение присваивается переменной.


это происходит, потому что это сообщение увеличивается. Это означает, что переменная увеличивается после вычисления выражения.

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

x теперь 10, но y-9, значение x до его увеличения.

более подробно см. определение приращения поста.


проверьте код ниже,

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

выход будет,

temp = 0
x = 0

post increment означает увеличьте значение и верните значение перед увеличением. Вот почему значение temp is 0. Ну и что, если ... --5--> и это в цикле (за исключением первой строки кода). прямо как в вопросе !!!!


оператор инкремента применяется к той же переменной, которой вы назначаете. Это напрашивается на неприятности. Я уверен, что вы можете увидеть значение вашей переменной x при запуске этой программы.... это должно прояснить, почему петля никогда не заканчивается.