Problem
i = 0; i = i++; Console.Write(i);
What's going to be the output written to the console, 0 or 1?
It's 0 (zero).
Although this is interesting, you should never use such a construct in your code as it's confusing.
| Test1 | |||
| Outcome | Average (sec.) | ||
| Variant1 | Passed | 0.000·032·699 | |
| Variant2 | Passed | -9.05% | 0.000·029·741 |
Interface
namespace PostIncrementAndAssignInteger
{
public interface IPostIncrementAndAssignInteger
{
int PostIncrementAndAssign();
}
}.class interface public abstract auto ansi PostIncrementAndAssignInteger.IPostIncrementAndAssignInteger
{
.method public hidebysig newslot abstract virtual
instance int32 PostIncrementAndAssign() cil managed
{
} // end of method IPostIncrementAndAssignInteger::PostIncrementAndAssign
} // end of class PostIncrementAndAssignInteger.IPostIncrementAndAssignIntegerTests
namespace PostIncrementAndAssignInteger.Tests
{
using Microsoft.VisualStudio.TestTools.UnitTesting;
using PostIncrementAndAssignInteger;
public class Tests
{
public void Test1<T>() where T : IPostIncrementAndAssignInteger, new()
{
IPostIncrementAndAssignInteger variant = new T();
Assert.AreEqual(0, variant.PostIncrementAndAssign());
}
}
}Variants
Variant1
| Best | Worst | Average | |
| O(1) | O(1) | O(1) | Time |
| O(1) | O(1) | O(1) | Space |
The result is 0 for Microsoft's Common Language Runtime.
Take a look at the IL code:
For the first line "var i = 0;", this is what's going on:
1. ldc.i4.0: pushes zero as a four byte integer on the stack
2. stloc.0: pops the value zero from the stack into i
For the second line "i = i++;", this is what's going on:
1. ldloc.0: loads i on the stack
2. dup: duplicates the value on the stack (so zero is duplicated)
3. ldc.i4.1: pushes one as a four byte integer on the stack
4. add.ovf: adds signed integer values from the top of the stack (zero and one) with overflow checking (overflow checking is irrelevant though in this case)
5. stloc.0: pops the value one from the top of the stack into i (so 1 == i at this point)
6. stloc.0: pops the other value, which was zero, from the top of the stack into i (so 0 == i at this point, since the second top most value from the stack was not changed)
So there you go, it's zero.
References
- http://en.wikipedia.org/wiki/List_of_CIL_instructions
- http://www.coderanch.com/how-to/java/PostIncrementOperatorAndAssignment
namespace PostIncrementAndAssignInteger
{
using System.Diagnostics.CodeAnalysis;
[SuppressMessage("StyleCop.CSharp.LayoutRules", "SA1512:SingleLineCommentsMustNotBeFollowedByBlankLine", Justification = "Alternatives")]
public sealed class Variant1 : IPostIncrementAndAssignInteger
{
public int PostIncrementAndAssign()
{
var i = 0;
i = i++;
// the above code is equivalent to the below code
// int old = i;
// i = i + 1;
// i = old;
return i; // 0
}
}
}.class public auto ansi sealed beforefieldinit PostIncrementAndAssignInteger.Variant1
extends [mscorlib]System.Object
implements PostIncrementAndAssignInteger.IPostIncrementAndAssignInteger
{
.custom instance void [mscorlib]System.Diagnostics.CodeAnalysis.SuppressMessageAttribute::.ctor(string,
string) = ( 01 00 1B 53 74 79 6C 65 43 6F 70 2E 43 53 68 61 // ...StyleCop.CSha
72 70 2E 4C 61 79 6F 75 74 52 75 6C 65 73 35 53 // rp.LayoutRules5S
41 31 35 31 32 3A 53 69 6E 67 6C 65 4C 69 6E 65 // A1512:SingleLine
43 6F 6D 6D 65 6E 74 73 4D 75 73 74 4E 6F 74 42 // CommentsMustNotB
65 46 6F 6C 6C 6F 77 65 64 42 79 42 6C 61 6E 6B // eFollowedByBlank
4C 69 6E 65 01 00 54 0E 0D 4A 75 73 74 69 66 69 // Line..T..Justifi
63 61 74 69 6F 6E 0C 41 6C 74 65 72 6E 61 74 69 // cation.Alternati
76 65 73 ) // ves
.method public hidebysig newslot virtual final
instance int32 PostIncrementAndAssign() cil managed
{
// Code size 15 (0xf)
.maxstack 3
.locals init ([0] int32 i,
[1] int32 CS$1$0000)
//000040: {
IL_0000: nop
//000041: var i = 0;
IL_0001: ldc.i4.0
IL_0002: stloc.0
//000042: i = i++;
IL_0003: ldloc.0
IL_0004: dup
IL_0005: ldc.i4.1
IL_0006: add.ovf
IL_0007: stloc.0
IL_0008: stloc.0
//000043:
//000044: // the above code is equivalent to the below code
//000045: // int old = i;
//000046: // i = i + 1;
//000047: // i = old;
//000048:
//000049: return i; // 0
IL_0009: ldloc.0
IL_000a: stloc.1
IL_000b: br.s IL_000d
//000050: }
IL_000d: ldloc.1
IL_000e: ret
} // end of method Variant1::PostIncrementAndAssign
.method public hidebysig specialname rtspecialname
instance void .ctor() cil managed
{
// Code size 7 (0x7)
.maxstack 8
IL_0000: ldarg.0
IL_0001: call instance void [mscorlib]System.Object::.ctor()
IL_0006: ret
} // end of method Variant1::.ctor
} // end of class PostIncrementAndAssignInteger.Variant1| Lines | Blocks | |||
| Covered | Partially covered | Not covered | Covered | Not covered |
| 5 | 0 | 0 | 2 | 0 |
Variant2
| Best | Worst | Average | |
| O(1) | O(1) | O(1) | Time |
| O(1) | O(1) | O(1) | Space |
You might think that the volatile keyword will forbid the kind of behavior exposed in the first variant, but it doesn't.
The volatile modifier is usually used for a field that is accessed by multiple threads without using the lock statement to serialize access.
Fields that are declared volatile are not subject to compiler optimizations that assume access by a single thread.
This ensures that the most up-to-date value is present in the field at all times.
References
namespace PostIncrementAndAssignInteger
{
using System.Diagnostics.CodeAnalysis;
public sealed class Variant2 : IPostIncrementAndAssignInteger
{
private volatile int i = 0;
public int PostIncrementAndAssign()
{
this.i = this.i++;
return this.i; // 0
}
}
}.class public auto ansi sealed beforefieldinit PostIncrementAndAssignInteger.Variant2
extends [mscorlib]System.Object
implements PostIncrementAndAssignInteger.IPostIncrementAndAssignInteger
{
.field private int32 modreq([mscorlib]System.Runtime.CompilerServices.IsVolatile) i
.method public hidebysig newslot virtual final
instance int32 PostIncrementAndAssign() cil managed
{
// Code size 43 (0x2b)
.maxstack 4
.locals init ([0] int32 CS$1$0000,
[1] int32 CS$0$0001)
.language '{3F5162F8-07C6-11D3-9053-00C04FA302A1}', '{994B45C4-E6E9-11D2-903F-00C04FA302A1}', '{5A869D0B-6611-11D3-BD2A-0000F80849BD}'
//000031: {
IL_0000: nop
//000032: this.i = this.i++;
IL_0001: ldarg.0
IL_0002: ldarg.0
IL_0003: dup
IL_0004: volatile.
IL_0006: ldfld int32 modreq([mscorlib]System.Runtime.CompilerServices.IsVolatile) PostIncrementAndAssignInteger.Variant2::i
IL_000b: dup
IL_000c: stloc.1
IL_000d: ldc.i4.1
IL_000e: add.ovf
IL_000f: volatile.
IL_0011: stfld int32 modreq([mscorlib]System.Runtime.CompilerServices.IsVolatile) PostIncrementAndAssignInteger.Variant2::i
IL_0016: ldloc.1
IL_0017: volatile.
IL_0019: stfld int32 modreq([mscorlib]System.Runtime.CompilerServices.IsVolatile) PostIncrementAndAssignInteger.Variant2::i
//000033:
//000034: return this.i; // 0
IL_001e: ldarg.0
IL_001f: volatile.
IL_0021: ldfld int32 modreq([mscorlib]System.Runtime.CompilerServices.IsVolatile) PostIncrementAndAssignInteger.Variant2::i
IL_0026: stloc.0
IL_0027: br.s IL_0029
//000035: }
IL_0029: ldloc.0
IL_002a: ret
} // end of method Variant2::PostIncrementAndAssign
.method public hidebysig specialname rtspecialname
instance void .ctor() cil managed
{
// Code size 17 (0x11)
.maxstack 8
//000028: private volatile int i = 0;
IL_0000: ldarg.0
IL_0001: ldc.i4.0
IL_0002: volatile.
IL_0004: stfld int32 modreq([mscorlib]System.Runtime.CompilerServices.IsVolatile) PostIncrementAndAssignInteger.Variant2::i
IL_0009: ldarg.0
IL_000a: call instance void [mscorlib]System.Object::.ctor()
IL_000f: nop
IL_0010: ret
} // end of method Variant2::.ctor
} // end of class PostIncrementAndAssignInteger.Variant2| Lines | Blocks | |||
| Covered | Partially covered | Not covered | Covered | Not covered |
| 5 | 0 | 0 | 3 | 0 |