What is the explanation for the result from the following operation?
k += c += k += c;
I was trying to understand the output result from the following code:
int k = 10; int c = 30; k += c += k += c; //k=80 instead of 110 //c=70
and currently I am struggling with understanding why the result for "k" is 80. Why is assigning k=40 not working (actually Visual Studio tells me that that value is not being used elsewhere)?
Why is k 80 and not 110?
If I split the operation to:
k+=c; c+=k; k+=c;
the result is k=110.
I was trying to look through the CIL, but I am not so profound in interpreting generated CIL and can not get a few details:
// [11 13 - 11 24] IL_0001: ldc.i4.s 10 IL_0003: stloc.0 // k // [12 13 - 12 24] IL_0004: ldc.i4.s 30 IL_0006: stloc.1 // c // [13 13 - 13 30] IL_0007: ldloc.0 // k expect to be 10 IL_0008: ldloc.1 // c IL_0009: ldloc.0 // k why do we need the second load? IL_000a: ldloc.1 // c IL_000b: add // I expect it to be 40 IL_000c: dup // What for? IL_000d: stloc.0 // k - expected to be 40 IL_000e: add IL_000f: dup // I presume the "magic" happens here IL_0010: stloc.1 // c = 70 IL_0011: add IL_0012: stloc.0 // k = 80??????
An operation like
a op= b; is equivalent to
a = a op b;. An assignment can be used as statement or as expression, while as expression it yields the assigned value. Your statement
k += c += k += c;
can be translated to the equivalent one (since the assignment operator is right-associative)
k += (c += (k += c));
10 → 30 → 10 → 30 // operand evaluation order k = k + (c = c + (k = k + c)); 40 ← 10 30 // operator evaluation order 70 ← 30 40 80 ← 10 70
Where during the whole evaluation the old values of the involved variables are used. This is especially true for the value of
k (see my review of the IL below and the link Wai Ha Lee provided). Therefore, you are not getting 70 + 40 (new value of
k) = 110, but 70 + 10 (old value of
k) = 80.
The point is that (according to the C# spec) "Operands in an expression are evaluated from left to right" (the operands are the variables in our case). This is independent of the operator precedence which in this case dictates an execution order from right to left. (See comments to Eric Lippert's answer on this page).
Now let's look at the IL. IL assumes a stack based virtual machine, i.e. it does not use registers.
IL_0007: ldloc.0 // k (is 10) IL_0008: ldloc.1 // c (is 30) IL_0009: ldloc.0 // k (is 10) IL_000a: ldloc.1 // c (is 30)
The stack now looks like this (from left to right; top of stack is right)
10 30 10 30
IL_000b: add // pops the 2 top (right) positions, adds them and pushes the sum
10 30 40
10 30 40 40
IL_000d: stloc.0 // k <-- 40
10 30 40
10 70 70
IL_0010: stloc.1 // c <-- 70
IL_0012: stloc.0 // k <-- 80
IL_000d: stloc.0, i.e. the first assignment to
k , could be optimized away. Probably this is done by the jitter when converting IL to machine code.
Note also that all the values required by the calculation are either pushed to the stack before any assignment is made or are calculated from these values. Assigned values (by
stloc) are never re-used during this evaluation.
stloc pops the top of the stack.