web-dev-qa-db-ja.com

なぜこれが無限ループに入るのですか?

私は次のコードを持っています:

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 について読んで理解しようとします...

489
Tom Brito

:C#を使用すると、intキーワードを参照してrefパラメーターを渡すことができるので、元々、説明のためにこの回答にC#コードを投稿しました。 Googleで見つけた最初の MutableInt クラスを使用して、C#でrefが行うことをおおまかにソートするために、実際の有効なJavaコードで更新することにしました。それが答えを助けているのか、傷つけているのか本当にわかりません。 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をインクリメントします。そして増分前の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.

上記の割り当ての左側にあるxyに置き換えると、「最初に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の値がxになる、次にx + 1、次にx 再び。


更新:ちなみに、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);
        }
    }
}

以下は、上記のプログラムの出力の抜粋です。 1と0の両方の不規則な発生に注意してください。

バックグラウンドスレッドを開始しています... 
 0 
 0 
 1 
 1 
 0 
 0 
 0 
 0 
 0 
 0 
 0 
 0 
 0 
 0 
 1 
 0 
 1 
352
Dan Tao

x = x++は次のように機能します。

  • 最初に式x++を評価します。この式を評価すると、式の値(インクリメント前のxの値)が生成され、xがインクリメントされます。
  • 後で式の値をxに割り当て、インクリメントされた値を上書きします。

したがって、イベントのシーケンスは次のようになります(javap -cによって生成される実際の逆コンパイルされたバイトコードであり、コメントがあります):

   8:iload_1 //スタック内のxの現在の値を記憶します
 9:iinc 1、1 // xをインクリメントします(スタックを変更しません)
 12:istore_1 //記憶された値を書き込みますx 
へのスタック

比較のため、x = ++x

   8:iinc 1、1 //増分x 
 11:iload_1 // xの値をスタックにプッシュします
 12:istore_1 //スタックからxに値をポップします
169
axtavt

これは、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が与えられます。

main()メソッドの ニーモニック を見てみましょう。

  • iconst_0:定数値0がスタックにプッシュされます。
  • istore_1:スタックの最上部の要素がポップアウトされ、インデックス1を持つローカル変数に格納されます
    これはxです。
  • iload_11であるxの値である場所0の値がスタックにプッシュされます。
  • iinc 1, 1:メモリ位置1の値は1ずつ増加します。したがって、x1になります。
  • istore_1:スタックの最上部の値は、メモリの場所1に保存されます。つまり、0xoverwritingの増分値に割り当てられます。

したがって、xの値は変化せず、無限ループになります。

104
codaddict
  1. プレフィックス表記は、式が評価される前に変数をインクリメントします。
  2. 後置記法は、式の評価後に増加します。

ただし、「=」は、「++」よりも演算子の優先順位が低くなります。

したがって、x=x++;は次のように評価する必要があります

  1. x割り当ての準備(評価済み)
  2. xインクリメント
  3. xに割り当てられたxの以前の値。
52
Jaydee

まったく当てはまらない答えはないので、ここに行きます:

int x = x++を書いているときは、xを新しい値に自分自身に割り当てるのではなく、xx++の戻り値に割り当てています表現。 Colin Cochrane's answer で示唆されているように、これは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の値を読み取ると、更新された値、つまり1を受け取ります。

34
Robert Munteanu

他の人によってすでに十分に説明されています。関連するJava仕様セクションへのリンクを含めるだけです。

x = x ++は式です。 Javaは 評価順序 に従います。最初に式x ++を評価します。これは xをインクリメントし、結果値をxの前の値に設定します です。次に、変数xに 式の結果を代入 します。最後に、xは以前の値に戻ります。

29
plodoc

この文:

x = x++;

次のように評価されます。

  1. xをスタックにプッシュします。
  2. インクリメントx;
  3. スタックからxをポップします。

したがって、値は変更されません。それと比較してください:

x = ++x;

次のように評価されます。

  1. インクリメントx;
  2. xをスタックにプッシュします。
  3. スタックからxをポップします。

あなたが欲しいのは:

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

答えは非常に簡単です。物事が評価される順序に関係しています。 x++xの値を返し、xをインクリメントします。

したがって、式x++の値は0です。したがって、ループ内で毎回x=0を割り当てています。確かにx++はこの値を増やしますが、それは割り当ての前に起こります。

10
Mike Jones

http://download.Oracle.com/javase/tutorial/Java/nutsandbolts/op1.html から

インクリメント/デクリメント演算子は、オペランドの前(前)または後(後)に適用できます。コード結果++;および++ result;両方とも結果が1ずつ増加します。唯一の違いは、プレフィックスバージョン(++ result)が増分値に評価されることです。一方、ポストフィックスバージョン(result ++)は元の値に評価されます。単純なインクリメント/デクリメントを実行するだけであれば、実際にどのバージョンを選択してもかまいません。ただし、より大きな式の一部でこの演算子を使用すると、選択した式によって大きな違いが生じる場合があります。

説明のために、以下を試してください。

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

1と0を出力します。

8
Colin Cochrane

何が起こっているのかを理解するのに、マシンコードは本当に必要ありません。

定義によると:

  1. 代入演算子は右辺式を評価し、一時変数に保存します。

    1.1。 xの現在の値がこの一時変数にコピーされます

    1.2。 xは現在インクリメントされています。

  2. その後、一時変数は式の左側にコピーされます。これは偶然です。そのため、xの古い値が再びコピーされます。

とても簡単です。

7
houman001

次の動作を効果的に取得しています。

  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.
7
RHSeeger

これは、この場合、インクリメントされないためです。 x++は、この場合のようにインクリメントする前に、最初にその値を使用します:

x = 0;

しかし、++x;を実行すると、これは増加します。

5
mezzie

x++の値が0であるため、値は0のままです。この場合、xの値が増加するかどうかは関係なく、割り当てx=0が実行されます。これにより、一時的にインクリメントされたxの値(「非常に短い時間」の場合は1)が上書きされます。

3
Progman

++がrhsにある場合、数値がインクリメントされる前に結果が返されます。 ++ xに変更すると、問題はなかったでしょう。 Javaは、これを最適化して、増分ではなく単一の操作(xからxへの割り当て)を実行します。

1
Steven

これは、他のユーザーが期待する方法で機能します。プレフィックスとポストフィックスの違いです。

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

私が見る限り、増分値をオーバーライドする前の値で割り当てがオーバーライドされるため、エラーが発生します。つまり、増分が取り消されます。

具体的には、「x ++」式は、インクリメント後に「x」の値を持つ「++ x」とは対照的に、インクリメント前に「x」の値を持ちます。

バイトコードの調査に興味がある場合は、問題の3行を確認します。

 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を使用したバージョンではループしない理由です。 ++ xのバイトコードは、1年以上前に書いた1.3 Javaコンパイラーから覚えている限り、まったく異なるように見えるはずです。バイトコードは次のようになります。

iinc 1,1
iload_1
istore_1

したがって、最初の2行を入れ替えるだけで、インクリメント後のスタックの最上部に残っている値(つまり、式の「値」)がインクリメント後の値になるようにセマンティクスが変更されます。

1
micdah

X ++は、Xがインクリメントbeforeであったものを「返す」関数呼び出しと考えてください(これがポストインクリメントと呼ばれる理由です)。

したがって、操作の順序は次のとおりです。
1:インクリメントする前にxの値をキャッシュします
2:増分x
3:キャッシュされた値を返します(インクリメントされる前のx)
4:戻り値はxに割り当てられます

1
jhabbott
    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

もちろん、最終結果は、1行のx++;または++x;だけと同じです。

1
Paulpro

ポストがインクリメントされているために発生しています。これは、式が評価された後に変数が増分されることを意味します。

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

xは10になりましたが、yは9で、インクリメントされる前のxの値です。

詳細については、ポストインクリメントの定義を参照してください。

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

上記のステートメントにより、xは3に達することはありません。

0
Praveen Prasad

以下のコードを確認してください、

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

出力は

temp = 0
x = 0

post incrementは、値をインクリメントし、インクリメント前の値を返すを意味します。これが、値temp0である理由です。 temp = iとこれがループ内にある場合(コードの最初の行を除く)。質問のように!!!!

0
prime

Java ++では=(割り当て)よりも優先順位が高いためだと思います... http://www.cs.uwf.edu/~eelsheik/cop2253/resources/op_precedence.html ...を見てください.

X = x + 1 ... +と書く場合の同じ方法は、=(割り当て)よりも高い優先順位を持ちます。

0
cubob

値を1増やす前に、値が変数に割り当てられます。

0
kumara

x++式はxと評価されます。 ++部分は、-評価の後ではなく、評価の後の値に影響します。 x = x++は効果的に翻訳されます

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

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

無関係である必要があります。どちらの実装も有効でなければなりません、と思います。

私は、この動作に依存するコードを書くことに非常に慎重になります。それは実装に非常に依存しているように見えます。違いを生むのは、ここの例のように何かおかしなことをした場合、または2つのスレッドを実行していて、式内の評価の順序に依存していた場合だけです。

0
Jay