“簡單的.NET面試題”?以及IL代碼的用途
.NET面試題?
我的一名很好學(xué)的學(xué)生給我發(fā)來了一封郵件,其內(nèi)容如下:
==========================================================
你好!
感謝你給我的幫助!
有一個問題向你請教:
- for i as integer =1 to 10
- dim a as integer
- a=a+1
- next
在第二次循環(huán)結(jié)束時,a的值為多少?你是如何理解的?
非常感謝!
張XX
2009-8-12
============================================================
這是一段VB.NET代碼,雖然我在開發(fā)中不太有可能寫過這樣子的代碼——將一個變量的定義語句放到循環(huán)語句的內(nèi)部,但作為一名老的VB程序員,這道題看上去太簡單了,答案似乎一目了然。然而,如果真是這么簡單,這名學(xué)生會費(fèi)這么大功夫給我發(fā)這樣一個郵件?
先不看答案,大家猜猜,結(jié)果是什么?
(空掉數(shù)行,別偷看答案!)
……
……
……
……
……
……
……
……
……
……
真實結(jié)果是:2!相信這是一個會讓C#程序員大感意外的結(jié)果!難道不是每次循環(huán)開始時都新定義一個變量嗎?新定義的變量應(yīng)該取默認(rèn)值0啊,為何會得到2?
有關(guān)“.NET面試題”的分析
為了便于分析,我將代碼修改了一下,同時寫了一段C#和VB.NET代碼作為對比:
VB.NET代碼:
- Module Module1
- Sub Main()
- For i As Integer = 1 To 10
- Dim a As Integer
- a = a + 1
- Console.WriteLine(a)
- Next
- Console.ReadKey()
- End Sub
- End Module
C#代碼:
- class Program
- {
- static void Main(string[] args)
- {
- for (int i = 1; i <= 10; i++)
- {
- int a = 0; //必須初始化,否則C#編譯器報錯!
- a=a+1;
- Console.WriteLine(a);
- }
- Console.ReadKey();
- }
- }
運(yùn)行結(jié)果是:VB.NET程序輸出1到10,而C#程序輸出10個“1”。
原因何在?
有的程序員可能會想到可以使用Reflector工具反匯編上述兩段代碼生成的程序集,看看原因到底是什么。
然而你會很失望,對比結(jié)果看不出有什么大的差異,甚至Reflector根據(jù)IL指令為VB.NET程序生成的C#代碼還是錯的,無法通過C#編譯器的編譯。
“.NET面試題”全跟蹤:IL代碼解讀
最后一招:祭出“終極武器”——ildasm,直接閱讀生成的IL指令。
在Release模式下,VB.NET程序生成的IL代碼如下(我加了詳細(xì)的注釋,注意紅色的指令):
- .method public static void Main() cil managed
- {
- .entrypoint
- .custom instance void [mscorlib]System.STAThreadAttribute::.ctor() = ( 01 00 00 00 )
- // Code size 28 (0x1c)
- .maxstack 2
- //分配兩個Slot,用于保存兩個局部變量,對于整型變量,初值為0
- .locals init ([0] int32 a,
- [1] int32 i)
- //將“1”保存到變量”i”中
- IL_0000: ldc.i4.1
- IL_0001: stloc.1
- //將變量a的當(dāng)前值裝入計算堆棧
- IL_0002: ldloc.0
- //將“1” 裝入計算堆棧
- IL_0003: ldc.i4.1
- //實現(xiàn)a=a+1,add.ovf指令從堆棧中彈出兩個操作數(shù)相加,并進(jìn)行溢出檢查
- IL_0004: add.ovf
- //結(jié)果保存回變量a中
- IL_0005: stloc.0
- //將變量a的新值裝入計算堆棧
- IL_0006: ldloc.0
- //將a的新值輸出顯示
- IL_0007: call void [mscorlib]System.Console::WriteLine(int32)
- //將變量i的新值裝入計算堆棧
- IL_000c: ldloc.1
- //將”1”裝入計算堆棧
- IL_000d: ldc.i4.1
- //實現(xiàn)i=i+1,循環(huán)變量自增
- IL_000e: add.ovf
- //i的新值保存到變量i中
- IL_000f: stloc.1
- //將變量i的值裝入計算堆棧
- IL_0010: ldloc.1
- //將循環(huán)終值10壓入計算堆棧
- IL_0011: ldc.i4.s 10
- //如果i<=10,跳到指令I(lǐng)L_0002處重新執(zhí)行。
- IL_0013: ble.s IL_0002
- //暫停顯示
- IL_0015: call valuetype [mscorlib]System.ConsoleKeyInfo [mscorlib]System.Console::ReadKey()
- IL_001a: pop
- //退出
- IL_001b: ret
- } // end of method Module1::Main
而C#生成的如下,為簡潔起見,我只在關(guān)鍵語句加了注釋
- .method private hidebysig static void Main(string[] args) cil managed
- {
- .entrypoint
- // Code size 32 (0x20)
- .maxstack 2
- .locals init ([0] int32 i,
- [1] int32 a)
- //i=1
- IL_0000: ldc.i4.1
- IL_0001: stloc.0
- //無條件直接跳到IL_0014處!
- IL_0002: br.s IL_0014
- //a=0
- IL_0004: ldc.i4.0
- IL_0005: stloc.1
- //a++
- IL_0006: ldloc.1
- IL_0007: ldc.i4.1
- IL_0008: add
- IL_0009: stloc.1
- //輸出a的值
- IL_000a: ldloc.1
- IL_000b: call void [mscorlib]System.Console::WriteLine(int32)
- //i++
- IL_0010: ldloc.0
- IL_0011: ldc.i4.1
- IL_0012: add
- IL_0013: stloc.0
- IL_0014: ldloc.0
- //如果i<=10,跳轉(zhuǎn)到IL_0004處
- IL_0015: ldc.i4.s 10
- IL_0017: ble.s IL_0004
- IL_0019: call valuetype [mscorlib]System.ConsoleKeyInfo [mscorlib]System.Console::ReadKey()
- IL_001e: pop
- //結(jié)束返回
- IL_001f: ret
- } // end of method Program::Main
情況很清楚了,VB.NET編譯器充分利用了變量的默認(rèn)值,沒有生成直接的變量初始化語句,因此,它每次循環(huán)結(jié)束后跳到IL_0002處,其指令直接取出的就是變量a的當(dāng)前值,因此,每次循環(huán)的結(jié)果都可以保留,程序輸出結(jié)果“1”,“2”,……,“10”。
而C#則要求變量必須明確初始化,編譯器為變量a生成了初始化語句(IL_0004到IL_0005),而這兩個語句又在循環(huán)體內(nèi),每次循環(huán)開始a都回到初值0,因此,輸出10個“1”。
在IL代碼面前,編譯器玩的把戲被揭穿!
事實上,C#從2.0開始,就出現(xiàn)了許多讓不少初學(xué)者比較頭痛的語法,比如匿名方法、Lambda表達(dá)等,其實,只要使用Reflector或者是ildasm工具,你會發(fā)現(xiàn)這些與傳統(tǒng)語法相比“很奇怪”的新特性,在底層都會變成大家所熟悉的語法形式。
另外,從這個小實例中可以看到,掌握“比較底層”的IL編程,在了解.NET技術(shù)內(nèi)幕方面還是有幫助的。同時提醒一下.NET學(xué)習(xí)者,在學(xué)習(xí)中要重視掌握跟蹤調(diào)試的基本技能,我看到的幾乎所有的軟件高手,大都是分析問題的高手,他們高超技能之一往往表現(xiàn)為能熟練應(yīng)用各種工具深入調(diào)試程序找到問題的關(guān)鍵,進(jìn)而開發(fā)出優(yōu)秀的程序。
本文來自bitfan(數(shù)字世界一凡人)的專欄:《一道可以成為.NET面試“必殺題”的“簡單問題”》。
【編輯推薦】