題記:本文純粹是出於研究的目的,實際上下面的那種寫法是根本不被提倡,也不會有人使用的.代碼的優劣在於保證效能和程式擴充性的情況下,本身表述清晰與否,一味的追求簡化是沒有任何好處的,除了讓你自己覺得很cool.當然,讀你代碼的人絕對不會那麼認為.
昨天在CSDN首頁看到一個文章,問,i=0;i=i++;最後得出的i等於多少?
答案很容易得出,Console.WriteLine(i)可以看到是0.
答案當然不是我們所關注的,為什麼會是0呢?如果是i=0;j=i++;那任何人都能得出j=0這個結果,但是這裡是給i自己賦值,但是i++這個自增操作又確實執行了,那最後為什麼會是i=0呢?隱約覺得應該是出棧順序造成的結果,猜測是沒有意義的,就讓我們看看IL代碼到底是怎麼回事.
這是一段簡單的C#代碼:
static void Main(string[] args)
{
int i = 0;
i = i++;
Console.WriteLine(i);
}
對應的IL代碼如下:.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
// Code size 17 (0x11)
.maxstack 3
.locals init ([0] int32 i)
IL_0000: nop
IL_0001: ldc.i4.0
IL_0002: stloc.0
IL_0003: ldloc.0
IL_0004: dup
IL_0005: ldc.i4.1
IL_0006: add
IL_0007: stloc.0
IL_0008: stloc.0
IL_0009: ldloc.0
IL_000a: call void [mscorlib]System.Console::WriteLine(int32)
IL_000f: nop
IL_0010: ret
} // end of method Program::Main
下面讓我們分析一下這段IL代碼.
到IL_003為止,這裡實現了一次資料的入棧操作,即把變數v0的值push到了ES寄存器裡.對應的C#代碼是int i = 0;
此時的棧情況如下:
[0] //此時v0=0
然後執行了dup操作,複製棧頂元素,這裡對應的操作我們可以理解為是i = i++;中的右邊i的出現.
此時的棧的情況如下:
[0,0] //v0=0
接下來是在ES中再申請4byte的空間,值為1.注意,這裡並未把值賦給變數,因為這正是常量的申明.
棧情況如下:
[v0,0,1] //v0=0
這裡的1的申明實際上是i++操作引起的.單獨的i++等價i+=1;如果我們查看IL的話,也會發現兩者是一模一樣的
隨後就進入了關鍵的部分:操作和出棧.
add執行的加操作,需要兩個參數,因為結果依然是在ES中儲存中,因此操作完後,棧的情況如下:
[0,0+1]
到現在,已經很清楚了,接下來的兩次出棧做的是同一件事,彈出棧頂元素,賦給v0.如下:
[0,0+1]-->pop 1 to v0,v0 = 1;
[0]-->pop 0 to v0,v0 = 0;
v0正是最終i的值,為0.
疑點:
為何最後會有兩次出棧操作?
答案是,在i = i++;中實際上有兩部操作,i = 右邊(未知數);i = i + 1;因此,必然存在兩次出棧.
最後,實際上在C/C++中i=i++後,得出的結果為1,至於為什麼,即使我們不反組譯碼也應該明白,肯定是出棧或入棧的順序導致的,有興趣的朋友不妨去試著分析一下:)