今天在群裡有人問到:
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 );
得到的IL代碼為:
.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.0IL_0001: stloc.0IL_0002: ldloc.0IL_0003: dupIL_0004: ldc.i4.1IL_0005: addIL_0006: stloc.0IL_0007: stloc.1IL_0008: ldloc.1IL_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++的結果)進行了拷貝(暫存),我想這樣理解問題就會簡單很多了
以上只代表個人觀點,歡迎拍磚:)