web-dev-qa-db-ja.com

自己割り当て内のポストインクリメント

i++ and ++i の違いは理解していますが、以下の結果が得られる理由がよくわかりません。

static void Main(string[] args)
{
    int c = 42;
    c = c++;
    Console.WriteLine(c);   //Output: 42
}

上記のコードでは、変数をそれ自体に割り当ててから値をインクリメントしているため、結果は43になると予想されます。ただし、42を返しています。 c = c--;を使用しても同じ結果が得られます。

c++;を使用するだけで完了できることはわかっていますが、なぜそれがそのように動作しているのか、もっと興味があります。誰かがここで何が起こっているのか説明できますか?

35
Siyual

そのための中間言語コードを見てみましょう。

IL_0000:  nop
IL_0001:  ldc.i4.s    2A
IL_0003:  stloc.0     // c
IL_0004:  ldloc.0     // c

これにより、定数整数42がスタックにロードされ、次に変数cに格納され、すぐにスタックに再度ロードされます。

IL_0005:  stloc.1
IL_0006:  ldloc.1

これにより、値が別のレジスタにコピーされ、再度ロードされます。

IL_0007:  ldc.i4.1
IL_0008:  add

これにより、ロードされた値に定数1が追加されます

IL_0009:  stloc.0     // c

…そして結果(43)を変数cに格納します。

IL_000A:  ldloc.1
IL_000B:  stloc.0     // c

次に、もう一方のレジスタの値がロードされ(42のままです!)、変数cに格納されます。

IL_000C:  ldloc.0     // c
IL_000D:  call        System.Console.WriteLine
IL_0012:  nop
IL_0013:  ret

次に、値(42)が変数からロードされ、出力されます。


したがって、これからわか​​るのは、c++が変数を1つインクリメントする間after結果が返されましたが、そのインクリメントは変数に値を割り当てる前に発生するということです。したがって、シーケンスは次のようになります。

  1. cから値を取得します
  2. ポストインクリメントc
  3. 以前に読み取った値をcに割り当てます

そしてそれはあなたがその結果を得る理由を説明するはずです:)


もう1つの例を追加するには、これが削除されたコメントで言及されているためです。

c = c++ + c;

これは非常によく似ています。初期値を2とすると、加算の左側が最初に評価されます。したがって、値は変数(2)から読み取られ、次にcがインクリメントされます(cは3になります)。次に、加算の右側が評価されます。 cの値が読み取られます(現在は3)。次に、加算が行われ(2 + 3)、結果(5)が変数に割り当てられます。


これからのポイントは、通常の式でインクリメント操作とデクリメント操作を混在させないようにする必要があるということです。動作は非常に明確に定義されており(上記のように)絶対的に理にかなっていますが、それでも頭を包むのが難しい場合があります。特に、式でインクリメントするのと同じ変数に何かを割り当てると、すぐに混乱します。ですから、自分自身や他の人に有利に働き、完全に自分自身ではない場合はインクリメント/デクリメント操作を避けてください:)

34
poke

C#演算子に関するMSDNページ によると、代入演算子(=)は、++xx++などの主要な演算子よりも優先順位が低くなります。つまり、行に

c = c++;

右側が最初に評価されます。式c++cを43にインクリメントし、結果として元の値42を返します。これは、割り当てに使用されます。

あなたが州にリンクした文書として、

[2番目の形式は]接尾辞インクリメント操作です。演算の結果は、インクリメントされる前のオペランドの値です。

言い換えれば、あなたのコードはと同等です

// Evaluate the right hand side:
int incrementResult = c;   // Store the original value, int incrementResult = 42
c = c + 1;                 // Increment c, i.e. c = 43

// Perform the assignment:
c = incrementResult;       // Assign c to the "result of the operation", i.e. c = 42

これをプレフィックス形式と比較してください

c = ++c;

これは次のように評価されます

// Evaluate the right hand side:
c = c + 1;                 // Increment c, i.e. c = 43
int incrementResult = c;   // Store the new value, i.e. int incrementResult = 43

// Perform the assignment:
c = incrementResult;       // Assign c to the "result of the operation", i.e. c = 43
20
CompuChip

ドキュメントは、接尾辞の状態について次のように述べています。

演算の結果は、インクリメントされる前のオペランドの値です。

これは、次のことを行う場合を意味します。

c = c++;

実際に42cに再割り当てしているので、コンソールに42が出力されます。しかし、あなたがそうするならば:

static void Main(string[] args)
{
    int c = 42;
    c++;
    Console.WriteLine(c);  
}

43が出力されます。

コンパイラが生成する (デバッグモード)を見ると、次のことがわかります。

private static void Main(string[] args)
{
    int num = 42;
    int num2 = num;
    num = num2 + 1;
    num = num2;
    Console.WriteLine(num);
}

これは、上書きをより明確に示しています。リリースモードを見ると、コンパイラが次の呼び出し全体を最適化していることがわかります。

private static void Main(string[] args)
{
    Console.WriteLine(42);
}
5
Yuval Itzchakov

代入の右辺の式が完全に評価された後、代入が実行されます。

   c = c++;

と同じです

   // Right hand side is calculated first.
   _tmp = c;
   c = c + 1;

   // Then the assignment is performed
   c = _tmp;
3
user1023602

...これは変数をそれ自体に割り当ててから値をインクリメントするためです...

いいえ、それはそれがすることではありません。

post-increment演算子は変数をインクリメントし、old値を返しますpre-increment演算子は変数をインクリメントし、new値を返します

だからあなたのc++はcを43にインクリメントしますが、42を返し、それが再びcに割り当てられます。

3
René Vogt

元の質問者が何を考えていたのかがわかります。彼らは(私が思うに)ポストインクリメントとは、式全体の評価後に変数をインクリメントすることを意味すると考えていました。それ

x = a[i++] + a[j++];   // (0)

と同じです

{ x = a[i] + a[j] ; i += 1 ; j += 1; }   // (1)

(そして確かにこれらは同等です)そしてそれ

c = c++;  // (2)

手段

{ c = c ; c +=1 ; } // (3)

そしてそれ

x = a[i++] + a[i++];  // (4)

手段

{ x = a[i] + a[i] ; i += 2 ; } // (5)

しかし、そうではありません。 v++は、すぐにvをインクリメントすることを意味しますが、式の値として古い値を使用します。したがって、(4)の場合、実際に同等のステートメントは次のようになります。

{int t0 = a[i] ; i += 1 ; int t1 = a[i] ; i += 1 ; x = t0 + t1 ; } // (6)

他の人が指摘しているように、(2)や(4)のようなステートメントはC#(およびJava)で明確に定義されていますが、CおよびC++では十分に定義されていません。

(2)や(4)のように変数を変更し、それを他の方法で使用するCおよびC++式では、通常は定義されていません。すべて、例えば銀行口座からコンパイラの作成者に送金します。

3