(这系列文章主要以我的读书笔记为基础写成的,因此可能跳跃性比较大。我想写的东西不只是包含一些结论,还包括了自己在学习时的所思所想。其中肯定有不妥之处,希望能得到指正。)


编译器是什么?这个问题在我自学的开始阶段的确困扰了我很久。简单的说,就是把高级语言翻译成低级语言的东西。高级语言我喜欢叫“源代码”。就如同一般的翻译一样,翻译的东西可能会夹带写译者的想法,所以你写的东西和实际被翻译出来的东西未必就是一样的。比如说一个delegate关键字,经过C#编译器编译之后,在IL中就表现成了一个有着4个方法的类,而且该类符合异步编程模型的规定。其实还有很多地方带有译者的思想,比如对字符串常量"a"+"a"编译成"aa"(非字符串常量不会连接),循环的时候用一个临时变量来代替对.length属性的访问(因为属性归根到底还是属于方法,访问的代价要大),以及对null的处理,对闭包的处理等等……了解译者(编译器)的想法,挺重要。


从C#的源代码到机器代码,中间要经过两个编译器。一个就是把C#源代码编译成托管模块的C#编译器。另一个就是作为CLR一个组件的JIT编译器。每经过一次编译,程序的语言就更低级一步。


搞清楚了有两个编译器的存在是理解接下来的内容的第一步。C#编译器生成的是IL语言和元数据。只要你喜欢,你也可以自己做一个编译器,只要生成的是IL语言和源代码,都叫面向CLR的编译器,也就是可以通过CLR来执行。为什么需要通过CLR来管理托管模块的执行?因为IL虽然比C#源代码低级些,可毕竟仍然不是机器语言,机器仍然不理解如何去处理IL代码。这时候,就需要一个程序把IL翻译成机器能懂的CPU指令,而这个程序就叫做CLR。当然,这只是CLR的部分功能。所以,你可以把它看成一种特殊的程序,CLR是需要运行的。托管代码都需要在CLR上面运行,因为没有CLR的JIT即时编译,机器是不懂你的程序的。所以如果你用C#编写的Winform程序(程序集),虽然是以.exe结尾,可是如果没有安装.NET Framework,是不能运行的。既然CLR需要运行,那么它就会被加载到内存当中,这个工作的具体流程后面会讲到。

但是到这里我们可以猜出来加载顺序

1.Windows创建进程加载CLR 

2.CLR加载我们编写的程序集


另一个容易搞混的概念就是”公共语言运行时“和运行时这两个词。公共语言运行时指的就是CLR,而运行时就是……不懂怎么描述,就是程序被加载到内存并被处理器处理的时候。


选择不同的语言,有时候就代表着不同的编成思想,虽然最后生成的都是IL代码和元数据。编译器这时候又充当起检验你是否正确的表达了你的意图的一种工具。由编译器所保证的类型安全也是静态语言的优势之一。


最后再强调一下C#编译器和JIT编译器的区别。C#编译器是用来生成包含IL和元数据的模块的,一旦编译完成,东西基本就是定下来了的。而JIT编译器会根据元数据和情况来做一些改变。比如泛型中的类型参数的填入和生成相应的机器代码,都是JIT编译器做的,而不是C#编译器。C#编译器的工作就是生成相应的IL和元数据告诉JIT这是一个泛型类型,有类型实参。编译好的程序集,放在那里不动,程序集里的IL和元数据当然不会发生变化。而同一个程序集,在不同CLR版本中的行为和性能就有可能不同,也是一样的道理。


下一篇我想讲一下运行过程,说到这我发现CLR VIA C#这本书的编排很合理,因为有些地方绕不过去就是绕不过去。这对后面的理解很重要。其实我原来想说下泛型的,但是不理解程序是怎么加载的,在内存中是怎么表现的,就比较难理解接下来的东西。

posted on 2011-06-08 22:07  一路转圈的雪人  阅读(3047)  评论(5编辑  收藏  举报