详细介绍C#编译器

开发 后端
本文介绍C#编译器,以及介绍无论你在哪个范围,在什么时候开始声明,实际上都是在函数的一开始用一个.locals这样的伪语句来声明的。

本文讲述C#编译器的一些问题,目的是防止错误使用本地变量。但是据我研究,这里面有“Bug”(注意双引号),那么会有什么有趣的“Bug”呢?首先大家看下一个简单的例子:

  1. publicvoidTest()  
  2. {  
  3. {  
  4. inta;  
  5. }  
  6. {  
  7. inta;  
  8. }  
  9. }  

在这个Test函数里面有两对打括号,标明两个互不相属的子范围。这里大家也许看的非常不习惯,因为没有人光秃秃的写这么两对大括号的。我跟大家说:没关系,编译器承认光秃秃的大括号的,这个也是标准C里面的规范之一,作用就是把大括号里面的所有东西认为是“一句话”,准确点讲是逻辑语句,同时内部是一个范围,约束范围内的本地变量不会往外传播。如果大家实在看不习惯了,可以自行加上诸如while(true)之类的前缀,就习惯了。

那么这段代码有什么Bug呢?没有,确实没有Bug,编译顺利通过。当然,显示了两个Warning,说a没有被用到,无伤大雅。我们首先来分析一下,编译器怎么给把这个给弄通过的呢?我们用Reflector来看一下(当然,因为没有切实的代码,所以只能够看IL,而不能够看C#):

  1. publichidebysiginstancevoidTest()cilmanaged  
  2. {  
  3. //CodeSize:2byte(s)  
  4. .maxstack0  
  5. .locals(  
  6. int32num1,  
  7. int32num2)  
  8. L_0000:nop  
  9. L_0001:ret  
  10. }  

哦!原来编译器把内部的变量改名字了!或者说编译器把他们当作完全不同的两个变量来对待。同时我们在这里也可以看出来,实际上在IL里面时不区分范围的,只有本地变量着一个简单的概念。无论你在哪个范围,在什么时候开始声明,实际上都是在函数的一开始用一个.locals这样的伪语句来声明的。这么做是简单省事的办法,因为如果在用户源代码实际声明的地方才在栈上面开辟空间,那么最后函数退出的时候就不知道该释放多少栈空间了。当然这不是不可以解决的,但是那样的话增加了不必要的复杂度。如果我来设计.NET Framework,我也会通过高级语言的编译器来约束范围问题,而不是摆到IL里面去解决。(毕竟IL里面没有这样的功能不影响我们写程序)稍微引申一下,我们就知道,一个函数里面有多少个本地变量,取决于整个函数内部声明了多少本地变量,而与变量所在范围无关。在IL这一层里面暂时我们没有看到这样的优化工作,我们可以看看这样的代码最后被编译器编译成什么了(用Release模式编译):

  1. publicintTest()  
  2. {  
  3. intb;  
  4. b=newRandom().Next(5);  
  5. if(b<5)  
  6. {  
  7. inta=newRandom().Next(5);  
  8. Console.WriteLine(a);  
  9. b=a;  
  10. }  
  11. else  
  12. {  
  13. inta=newRandom().Next(10);  
  14. Console.WriteLine(a);  
  15. b=a;  
  16. }  
  17. returnb;  
  18. }  

Reflector 反编译结果:

  1. publicintTest()  
  2. {  
  3. intnum1=newRandom().Next(5);  
  4. if(num1<5)  
  5. {  
  6. intnum2=newRandom().Next(5);  
  7. Console.WriteLine(num2);  
  8. returnnum2;  
  9. }  
  10. intnum3=newRandom().Next(10);  
  11. Console.WriteLine(num3);  
  12. returnnum3;  

大家可以看到num1是b,num2和num3则是分别的两个a。事实上这两个a互相之间是没有任何冲突的,也就是说是完全可以重用的,编译原理里面也有一个变量重用的优化,但是这里看不到有这样的优化,我觉得比较吃惊。虽然说这也可以算是一种Bug(严格说来是也不是),但是我要说的“Bug”不是这个。

分析完上面这些基本知识,我就来劲了:

  1. publicvoidTest()  
  2. {  
  3. {  
  4. inta;  
  5. }  
  6. {  
  7. inta;  
  8. }  
  9. inta;  

看,编译出来之后却出现了错误:
error CS0136: A local variable named 'a' cannot be declared in this scope because it would give a different meaning to 'a', which is already used in a 'child' scope to denote something else
哦,原来这个跟声明的顺序还没有关系,只要子范围里面有a了,那就不能够再定义这个变量了。这个难道跟IL里面所有变量都在函数开始部分声明有关系?看起来好像是这么一回事,但是实际上不是,因为C#编译器完全可以像前面那样,把最后一个a当作另外一个变量。这到底是怎么回事呢?我们需要作本次探索的最后一个实验:

  1. publicvoidTest()  
  2. {  
  3. a=2;  
  4. {  
  5. inta;  
  6. }  
  7. {  
  8. inta;  
  9. }  
  10. inta;  

这下可好,除了刚才那个错误之外,还多出来另外一个:
error CS0103: The name 'a' does not exist in the class or namespace 'ConsoleApplication1.Class2'
也就是说,编译器根本就没有把后面那个a当作从函数一开始的地方定义来看待。但是这两个错误合起来反而容易让我们产生这样的错觉和悖论:
因为前面两个a在范围外面就应该消失其影响力,那就不应该跟后面的a产生冲突。但现在既然你说了,第三个a的定义根前面那两个a的其中某一个定义相冲突了,那我就只能够认为后面这个a实际上在前两个a被定义出来之前就已经存在了,因为后面这个a处于外层范围,它不会在内层范围失去作用之前失效,这样还能够解释得通。可是这么解释我只能够认为外层的a应该在函数一开始的地方就生效了(老式的C编译器有一段时间确实是这样的),可是偏偏还来一个CS0103错误!解释不通,有“Bug”!

最后我来修正这个我一开始提出的说法,其实并没有Bug。得出有Bug的结论,那是从纯粹的语法角度看这个问题的,我也觉得应该容许在第三个a的定义出现,顶多只给出一个Warning。但是微软却给出了一个错误,我想这是从避免不必要的Bug的角度考虑,尽量保护开发人员避免不必要的烦恼。开发人员确实很有可能在定义了第三个a的时候忘记第一二个a已经失效了,同时也忘记了自己定义过第三个a,还以为自己用的是第一个或者第二个a里面的数据。不过对于这种解释,我还是有意见的:既然约束已经缩窄到这个地步了,那为什么要允许第二个a的定义呢?如果开发人员会忘记自己定义过第三个a,有什么理由认为不会把第二个a的定义给忘记了,以为自己在用第一个a呢?

本来上面所写的那些统统都是垃圾代码,我认为,在一个函数内部根本就不应该有相同的变量来迷惑自己。C#编译器在这些问题方面确实有相当严谨的考虑,不过我还是觉得有一些“悖论”存在,如果能够更加严谨,我认为只会更好。

责任编辑:佚名 来源: 天极开发
相关推荐

2009-08-14 11:34:26

Mono C#编译器

2009-09-01 10:35:19

C# 3.0编译器

2009-08-10 17:12:54

C#编译器

2009-08-18 11:27:56

配置C#命令行编译器

2009-08-14 16:37:02

C# NGWS run

2010-01-21 09:26:53

CC++编译器

2009-08-10 16:30:56

C# BitmapDa

2009-08-12 15:34:40

C# DBNull

2010-05-28 14:55:17

Linux编程工具

2010-01-18 10:34:21

C++编译器

2010-01-21 09:11:38

C++编译器

2009-12-24 10:04:38

Linux进行C编译

2009-08-13 13:38:30

C#命名规范

2009-08-14 17:04:50

C#类型系统

2009-08-07 16:10:20

C#调用API

2009-08-21 09:23:11

C# GDI+

2009-08-03 18:49:17

C#和Java

2009-08-26 17:31:59

C# const常量

2009-08-21 15:16:23

C#使用指针

2009-08-24 18:21:23

C# ListView
点赞
收藏

51CTO技术栈公众号