一道可以成为.NET面试“必杀题”的“简单问题”
我的一名很好学的学生给我发来了一封邮件,其内容如下:
==========================================================
你好!
感谢你给我的帮助!
有一个问题向你请教:
for i as integer =1 to 10
dim a as integer
a=a+1
next
在第二次循环结束时,a的值为多少?你是如何理解的?
非常感谢!
张XX
2009-8-12
============================================================
这是一段VB.NET代码,虽然我在开发中不太有可能写过这样子的代码——将一个变量的定义语句放到循环语句的内部,但作为一名老的VB程序员,这道题看上去太简单了,答案似乎一目了然。然而,如果真是这么简单,这名学生会费这么大功夫给我发这样一个邮件?
先不看答案,大家猜猜,结果是什么?
(空掉数行,别偷看答案!)
……
……
……
……
……
……
……
……
……
……
真实结果是:2!相信这是一个会让C#程序员大感意外的结果!难道不是每次循环开始时都新定义一个变量吗?新定义的变量应该取默认值0啊,为何会得到2?
为了便于分析,我将代码修改了一下,同时写了一段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();
}
}
运行结果是:VB.NET程序输出1到10,而C#程序输出10个“1”。
原因何在?
有的程序员可能会想到可以使用Reflector工具反汇编上述两段代码生成的程序集,看看原因到底是什么。
然而你会很失望,对比结果看不出有什么大的差异,甚至Reflector根据IL指令为VB.NET程序生成的C#代码还是错的,无法通过C#编译器的编译。
最后一招:祭出“终极武器”——ildasm,直接阅读生成的IL指令。
在Release模式下,VB.NET程序生成的IL代码如下(我加了详细的注释,注意红色的指令):
.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的当前值装入计算堆栈
IL_0002: ldloc.0
//将“1” 装入计算堆栈
IL_0003: ldc.i4.1
//实现a=a+1,add.ovf指令从堆栈中弹出两个操作数相加,并进行溢出检查
IL_0004: add.ovf
//结果保存回变量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
//实现i=i+1,循环变量自增
IL_000e: add.ovf
//i的新值保存到变量i中
IL_000f: stloc.1
//将变量i的值装入计算堆栈
IL_0010: ldloc.1
//将循环终值10压入计算堆栈
IL_0011: ldc.i4.s 10
//如果i<=10,跳到指令IL_0002处重新执行。
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#生成的如下,为简洁起见,我只在关键语句加了注释
.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,跳转到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
//结束返回
IL_001f: ret
} // end of method Program::Main
情况很清楚了,VB.NET编译器充分利用了变量的默认值,没有生成直接的变量初始化语句,因此,它每次循环结束后跳到IL_0002处,其指令直接取出的就是变量a的当前值,因此,每次循环的结果都可以保留,程序输出结果“1”,“2”,……,“10”。
而C#则要求变量必须明确初始化,编译器为变量a生成了初始化语句(IL_0004到IL_0005),而这两个语句又在循环体内,每次循环开始a都回到初值0,因此,输出10个“1”。
在IL代码面前,编译器玩的把戏被揭穿!
事实上,C#从2.0开始,就出现了许多让不少初学者比较头痛的语法,比如匿名方法、Lambda表达等,其实,只要使用Reflector或者是ildasm工具,你会发现这些与传统语法相比“很奇怪”的新特性,在底层都会变成大家所熟悉的语法形式。
另外,从这个小实例中可以看到,掌握“比较底层”的IL编程,在了解.NET技术内幕方面还是有帮助的。同时提醒一下.NET学习者,在学习中要重视掌握跟踪调试的基本技能,我看到的几乎所有的软件高手,大都是分析问题的高手,他们高超技能之一往往表现为能熟练应用各种工具深入调试程序找到问题的关键,进而开发出优秀的程序。
注:
有关IL编程的基础知识,请参考本人拙著《.NET 2.0面向对象编程揭秘》。另外,使用MSDN,可以查询到MSIL中所有指令的技术特性。我将在新著中也会对IL编程作介绍。
=========================================================
看了些网友的回复,感到我可能表达得不够清楚,他们可能并没有了解本文要表达的意思:
1 之所以说这道题“必杀”是因为它考核的是两种语言编译器的具体实现方式,过于偏了,如果作为面试题被习惯了C#的程序员看到,估计10人里面有9人会弄错。这说明“思维定式”有时会让人犯错误的,看问题和分析问题要尽量“客观”而不要“主观”,不要轻易地得出结论。
2 通篇我并未说:掌握语言特性需要分析其具体编译实现入手,而只是说:想弄明白一些东西,得去分析编译器生成出来的最终指令,因此下点功夫掌握MSIL并非无用,强调要重视对调试能力的培养。
3 有一名网友提到了“标准”问题,“标准”要为大家所遵循才有用,如果是“标准”,但实际上大家各行其事,“标准”就是一纸空文,这种事在IT业还少吗?因此,还是要根据具体情况进行具体分析,象TCP/IP,用的人多,也就成了标准。事实上,实践是起决定性作用的。
分享到:
相关推荐
.net面试题.net面试题.net面试题.net面试题.net面试题.net面试题.net面试题.net面试题.net面试题.net面试题
.net面试题.net面试题.net面试题.net面试题.net面试题.net面试题.net面试题.net面试题
.net秘笈 .net面试大全 .net面试精选 .net面试题
.net面试题 大全 .net面试 经典面试题
.net面试机试题集锦.net面试机试题集锦.net面试机试题集锦
.NET c# 编程 .net framework 面试 .NET c# 编程 .net framework 面试 .NET c# 编程 .net framework 面试 各地综合.net面试题 各地综合.net面试题 各地综合.net面试题 各地综合.net面试题
net高概率面试题 100个常见的.net面试题
注意,只有题目没有回答:部分题目如下: 面试题1 介绍ASP.NET ...面试题10 什么是.NET中的私有程序集 私有程序集就是不同项目中生成的程序集,仅供本项目使用,或者可以经过配置被某一个其它项目的程序集引用。
2008/06/17 18:37 4,246 .net 最新面试题.txt 2008/06/17 18:38 7,017 .net 面试题(高级开发人员篇).txt 2008/06/17 18:44 3,868 .net 面试题系列九.txt 2008/06/17 18:41 2,804 .net 面试题系列四(附答案).txt ...
初级程序员参加.net面试常常被问到的面试题。
c#,.net 程序员常见面试题大全(含答案) 程序员求职前的必不可少的准备工夫。
.net面试题4.net面试题4.net面试题4.net面试题4.net面试题4.net面试题4.net面试题4.net面试题4.net面试题4
.net面试题2.net面试题2.net面试题2.net面试题2.net面试题2.net面试题2.net面试题2.net面试题2
所有java面试题集以及.net 面试题文档 所有java面试题集以及.net 面试题文档 所有java面试题集以及.net 面试题文档 所有java面试题集以及.net 面试题文档
全部都是面试的宝典; c# asp.net .net .net面试题 c#面试题 c# asp.net .net .net面试题 c#面试题
.net面试题1.net面试题1.net面试题1.net面试题1.net面试题1.net面试题1
.net经典面试题ASP.NET面试集锦 绝对够全绝对经典 含答案
JAVA 和.NET面试题.rarJAVA 和.NET面试题.rarJAVA 和.NET面试题.rarJAVA 和.NET面试题.rarJAVA 和.NET面试题.rarJAVA 和.NET面试题.rarJAVA 和.NET面试题.rarJAVA 和.NET面试题.rarJAVA 和.NET面试题.rarJAVA 和.NET...
.net面试题3.net面试题3.net面试题3.net面试题3.net面试题3
几道常见的.net面试题几道常见的.net面试题几道常见的.net面试题几道常见的.net面试题几道常见的.net面试题