天天看點

C#和Java中的i=i++問題分析與備忘

昨天看見部門面試新員工的一道面試題,題目如下:

int i =1;

i=i++;

console.write(i);

/

我想當然的認為,這隻是一道簡單的自增問題,即先指派再自增,最後j的值為2,然而正确答案卻出乎我的意料,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

在查閱了一些相關資料之後,對這個問題有了一定的認識。與其說這是C#(包括Java)與C++語言的差別,不如說這是兩種編譯器之間的差别,為了替編譯器在優化代碼的過程中去掉一些不必要的限制,C,C++等編譯器都不約而同的保留了一些Undefine的文法元素,它們的執行邏輯随編譯器的不同而不同。而本文的問題就是一個例子,C#編譯器按照以下的邏輯分析表達式:

Instructions    Stack      Variable

                           1

ldloc i         1          1

dup             1, 1       1

ldc.i4.1        1, 1, 1    1

add             1, 2       1

stloc i         1          2

stloc i                    1

而C++的處理邏輯是這樣的:

Instructions    Stack   Variable

                        1

ldloc i         1       1

stloc i                 1

ldloc i         1       1

ldc.i4.1        1, 1    1

add             2       1

stloc i                 2

其中dup指令的解釋為:duplicate the top value of the stack (拷貝棧頂的值)

本來棧中的值為:1,執行過dup指令後棧中的值就變成:1|1了(|表示棧中各個值間的分隔,這裡表示棧中有兩個值)

可以看出就因為這個dup指令才導制了最後i的值還是變成了1,值得注意的是,隻有指派運算,才會去執行dup指令,如果僅僅是純粹的自增,則C#編譯器和C++編譯器的處理過程卻是一樣的。

那麼為什麼會出現這樣的情況呢?

Java中有一段類似此問題解釋:

在這裡jvm裡面有兩個存儲區,一個是暫存區(是一個堆棧,以下稱為堆棧),另一個是變量區。

語句istore_1是将堆棧中的值彈出存入相應的變量區(指派);語句iload_1是将變量區中的值暫存如堆棧中。

因為i = i++;是先将i的值(1)存入堆棧,然後對變量區中的i自加1,這時i的值的确是2,但是随後的istore_1又将堆棧的值(1)彈出賦給變量區的i,是以最後i =1。

又因為i = ++i;是先對變量區中的i自加1,然後再将變量區中i的值(2)存入堆棧,雖然最後執行了istore_1,但也隻是将堆棧中的值(2)彈出賦給變量區的i,是以i = ++i;的結果是i = 2。

我想CLR的實作機制應該是與JVM中基本相同的,也了解了為什麼結果不是2的原因了,對于這個問題,我覺得簡單的從運算符的定義去了解,更容易記憶。注意到MSDN中的一句言簡意赅話:

第一種形式是字首增量操作。該操作的結果是操作數加 1 之後的值。

第二種形式是字尾增量操作。該運算的結果是操作數增加之前的值。

i=i++與j=i++的本質是一樣的,都是用指派運算符左邊的變量去儲存指派運算符右邊變量運算之前的值,我想C#編譯器之是以用dup指令備份變量的值,正是出于這個目的。

轉載于:https://www.cnblogs.com/wangjisi/archive/2010/06/06/1752600.html