c语言编译器:中间语言(IL)和即时编译器(JIT)

对于.NET的初学者来,一个很令人困惑的问题是:从高级语言(如C#和VisualBasic)到托管语言再到机器语言究竟是一个怎样的过程。掌握这个过程也是理解.NET语言互操作性(也就是语言独立性的核心原则)的关键,并且也关系到二进制兼容性的问题。尽管本书一直尝试不探讨这些的底层细节实现而主要集中讲述如何最好地应用.NET,然而对CLR产生代码过程有一个概览对理解她的内部机理还是大有益处的。不仅如此,了解.NET产生代码的地过程还可以帮助解决一些特殊的安全性问题。

.NET语言的编译分为两个阶段.首先高级语言被编译成一种称作IL的中间语言,与高级语言相比,IL更像是机器语言,然而,IL却包含一些抽象概念(比如:类、异常),这也是这种语言被称为中间语言的原因。IL被打包在DLL或EXE文件中,而DLL和EXE在.NET中的主要区别就是:只有EXE可以直接被运行,而二者都可被某个正在执行的进程动态装载(后文详述)。由于机器的CPU只能执行本地汇编语言,而不是IL,进一步将IL编译成汇编语言的工作(也就是第二阶段)需要在运行时进行,这个过程由即时编译器(JIT)完成。

高级语言在初次被编译时,编译器做两件事:首先把编译得到的IL存储在DLL或EXE中,然后为类的每个方法创建一个stub函数,此函数会调用即时编译器,并将自身的地址作为参数传给编译器。即时编译器则从DLL或EXE中获取相应的IL,编译成机器语言,并将内存中的原零时调用函数替换成机器语言。这个过程的思想,是用已编译的方法调用未编译的方法,实质上被调用的是stub函数;stub函数再调用编译器,将自身编译为本地机器语言;最后,.NET会重新调用该方法,方法此时才被真正地执行。函数被反复调用时,机器指令会被直接执行,而只由编译器对方法进行初次编译需要花费时间。至于那些没有被调用的方法,则不会被编译。

当编译器生成一个EXE文件后,该程序的入口函数为Main()方法。装载器将这个EXE文件载入,探测到该这是一个托管EXE,于是又载入.NET运行时库文件(包括即时编译器),接着调用了EXE的Main()方法。这将触发对Main()方法的即时编译,Main()方法在内存中被替换为本地机器语言,于是.NET应用程序开始运行。在被编译为本地语言后,应用程序便可以自由调用本地代码了。当程序中止时,本地代码从内存中释放,所以在下次运行时,IL需要被即时编译器重新编译。

你可能会对即时编译对效率产生的影响而忧虑。然而这种忧虑应当减轻,因为这种编译方式比传统的静态代码编译要快。举例来说,即使编译器会区别特定类型的CPU(比如奔腾III和奔腾IV)并且根据CPU类型利用新增的指令集。相比较,传统的编译器产生代码必须依赖于最低层次的标准,比如386指令集,因此不能利用较新型号的CPU的功能。以后版本的即时编译器可能会被重写,以适应应用程序的编码方式(比如分支指令的使用频率,分支预测等等),然后重新编译代码,以优化某个特殊应用程序(甚至是某个用户!)对组件的使用方法。即时编译器还可以根据实际机器资源(比如内存和CPU速度),对它自己生成的代码进行优化。请注意,这些高级功能虽然目前还未实现,然而以后的机制很可能提供所有这些能力。总之,即时编译器的优化过程,是通过增加编译占用的时间换取应用程序执行效率。这种优化的效果取决于程序的调用方式和用途。在未来,这种代价可以在应用程序的安装过程中衡量,或者从用户偏好库中查找。
Tags:  c编译器 编译器 java编译器 c语言编译器

延伸阅读

最新评论

发表评论