初识CLR
眨眼间我已经实习了半年时间并且转正了,身份也正式从一个学生转变为一个职场人,这个博客自从开始实习以来就一直没有更新过= =没错,就是我懒癌晚期,不过不行!一切都要开始走向正轨,此博会继续见证我的成长,从现在开始吧!
最近刚开始看CLR via C#这本神作,本来以为自己会看不懂,之前我对这本书一直是“远观不可亵玩”的态度,当真正看了之后才发现许多事木有有你想象的那么难嘛~好,开始正文。
CLR,又叫通用语言运行时,又叫Common Language Runtime,你可以把他看作是一个虚拟机。来,咱先来看一个很经典的.NET Framework结构图:
我们按照层级关系一层层来看
第一层:
我们最熟悉的编程语言,.NET目前已经支持超过70中编程语言了,只要是.NET支持的编程语言都能享受CLR所带来的便利和优势。
第二层和第三层一起讲:
汇编器,这里的编译器指的不是通常意义的编译器,通常意义指的编译器是指可以直接将高级语言转换成目标机器语言,而这里的汇编器指的是将.NET支持的编程语言编译成托管代码(第三层)。那么问题来了。。。托管代码是什么鬼?可以这么理解,如果说.NET编译器生成的是托管代码,那我们平时学习的C/C++,编译器生成的就是非托管代码。托管代码包含IL和元数据(我擦。。。怎么又多了两个名词),IL指的是微软定义的中间语言(MSIL,Microsoft Intermediate Language,你可以理解为从高级语言到目标机器语言中间的一种代码)。CLR必须知道代码中的数据类型、类和他引用的类型和类的定义才能加载代码、管理内存、执行方法,所以这些信息就叫做metadata。所以第二道第三层的过程就简单的理解为把高级语言编译成中间语言即可。
第四层:
在这一层,CLR提供了运行时非常重要的一个环节即将中间语言编译成目标机器语言的功能。这里我们举个例子再来看一幅图深入讨论一下:
上图展示了一个方法首次调用时发生的事情,在Main方法执行之前,CLR会检测出Main代码所有的引用类型。CLR就会构造像右边那样的一个数据结构,里面是一些方法中用到的引用。这里存着每一个方法的入口,比如Console类中有WriteLine(),WriteLine(string)等,然后CLR会将其进行初始化,将这些入口指向一个未编档函数。这个函数就是JITCompiler。
当Main方法首次调用WriteLine时,JITCompiler会被调用,首先JIT会先申请一块稍后会用到的内存块,然后JITCompiler会在程序集的元数据中找到被调用方法的IL,并将将IL编译成本机的目标代码放入事先分配好的内存当中,接着JIT会修改最初调用它的这个引用指向之前分配好的内存并执行它,最后JIT会回到源代码继续执行下一条语句。这样一来,当Main方法之后再访问这个方法时就会直接去调用本机代码。所以这也是c#到底属于解释型还是编译型语言之争的源头,因为他是在执行的时候才去生成最终的目标代码的。讲到这里,许多人要开始质疑了,托管代码在运行时多了JIT这一步,而非托管代码是直接执行的目标代码,这效率肯定差一大截吧?事实并非如此,微软已经把JIT对性能的影响降到了最低,下面列举了托管代码由于非托管代码的一些地方:
- 当JIT生成本地代码时会根据当前硬件环境去使用当前cpu最优的指令,简单的来说就是根据不同cpu会做代码优化。
- JIT在代码运行时能够检测到代码运行的情况并分析进一步优化代码,比如CLR检测到一个if分支在99%的情况下是true,那么JIT就会重新编译,达到进一步的代码优化。(这一点据说还没有完全实现?有待考证,有知道的可以评论)
嗯。。今天要讲的差不多讲完了