今天在群里有人问到:
int i = 0; i = i++; Console.WriteLine( i );以上代码输出的结果是多少?
我很自以为是的回答是:1
可结果为什么不是1呢?先说一下我的分析思路
这段小程序生成的IL代码为:
.maxstack 3
.locals init (int32 V_0,
int32 V_1)
IL_0000: ldc.i4.0
IL_0001: stloc.0
IL_0002: ldloc.0
IL_0003: dup
IL_0004: ldc.i4.1
IL_0005: add
IL_0006: stloc.0
IL_0007: stloc.1
IL_0008: ldloc.1
IL_0009: call void [mscorlib]System.Console::WriteLine(int32)
IL_000e: ret
其中dup指令的解释为:duplicate the top value of the stack (拷贝栈顶的值)
本来栈中的值为:0,执行过dup指令后栈中的值就变成:0|0了(|表示栈中各个值间的分隔,这里表示栈中有两个值)
可以看出就因为这个dup指令才导制了最后i的值还是变成了0
那么是不是只要有++操作就会产生dup指令呢?于是我把代码改成以下形式:
int i = 0; i++; Console.WriteLine( i );
.maxstack 2 .locals init (int32 V_0) IL_0000: ldc.i4.0 IL_0001: stloc.0 IL_0002: ldloc.0 IL_0003: ldc.i4.1 IL_0004: add IL_0005: stloc.0 IL_0006: ldloc.0 IL_0007: call void [mscorlib]System.Console::WriteLine(int32) IL_000c: ret
可以看到并未出现dup指令,至此我们可以推断当把++之类(i++/++i)的结果值赋予一个变量时才会出现dup指令,为此我再进行了如下验证:
int i = 0; int j = i++; Console.WriteLine( j );
得到的IL代码为:
.maxstack 3
.locals init (int32 V_0,
int32 V_1)
IL_0000: ldc.i4.0
IL_0001: stloc.0
IL_0002: ldloc.0
IL_0003: dup
IL_0004: ldc.i4.1
IL_0005: add
IL_0006: stloc.0
IL_0007: stloc.1
IL_0008: ldloc.1
IL_0009: call void [mscorlib]System.Console::WriteLine(int32)
IL_000e: ret
dup指令再次出现了,同理我还验证了:
++i / i-- / --i
的情况
那么为什么会出现这样的情况呢?
Java中有一段类似此问题解释:
在这里jvm里面有两个存储区,一个是暂存区(是一个堆栈,以下称为堆栈),另一个是变量区。
语句istore_1是将堆栈中的值弹出存入相应的变量区(赋值);语句iload_1是将变量区中的值暂存如堆栈中。
因为i = i++;是先将i的值(0)存入堆栈,然后对变量区中的i自加1,这时i的值的确是1,但是随后的istore_1又将堆栈的值(0)弹出赋给变量区的i,所以最后i = 0。
又因为i = ++i;是先对变量区中的i自加1,然后再将变量区中i的值(1)存入堆栈,虽然最后执行了istore_1,但也只是将堆栈中的值(1)弹出赋给变量区的i,所以i = ++i;的结果是i = 1。
我想CLR的实现机制应该是与JVM中基本相同的,也理解了为什么结果不是1的原因了,可还有个疑问,不管Java还是C#为什么要这样设计呢?这样设计有什么好处吗?
然后我注意到了MSDN中的一句言简意赅话:
第一种形式是前缀增量操作。该操作的结果是操作数加 1 之后的值。
第二种形式是后缀增量操作。该运算的结果是操作数增加之前的值。
回头再来看我们的代码:
int j = i++;
j是什么?j就是i++的结果,MSDN中说明了i++的结果就是i在这条语句执行之前的值,很明显j就是0了,那么对于:
i = i++;
呢?这里的意思很明显就是i最后的结果就是i++的结果,i++的结果是0,所以i最后也就是0了,为了实现++运算符的定义(该运算的结果是操作数增加之前的值),所以在IL中使用dup指令对i之前的值(i++的结果)进行了拷贝(暂存),我想这样理解问题就会简单很多了
以上只代表个人观点,欢迎拍砖:)