编译器,是将便于人编写,阅读,维护的高级计算机语言翻译为计算机能识别,运行的低级机器语言的程序。编译器将源程序(Source program)做为输入,翻译产生使用目标语言(Target language)的等价程序。源程序通常为高级语言(High-level language),如Pascal,C++等,而目标语言则是汇编语言或目标机器的目标代码(Object code),有时也称做机器代码(Machine code)。html
一个现代编译器的主要工做流程以下:前端
源代码(sourcecode)→预处理器(preprocessor)→编译器(compiler)→汇编程序(assembler)→目标代码(objectcode)→链接器(Linker)→可执行程序(executables)程序员
编译语言与解释语言对比:正则表达式
许多人将高级程序语言分为两类:编译型语言和解释型语言。然而,实际上,这些语言中的大多数既可用编译型实现也可用解释型实现,分类实际上反映的是那种语言常见的实现方式。(可是,某些解释型语言,很难用编译型实现。好比那些容许在线代码更改的解释型语言。)算法
编译器是一种特殊的程序,它能够把以特定编程语言写成的程序变为机器能够运行的机器码。把一个程序写好,这时利用的环境是文本编辑器。这时我程序把程序称为源程序。在此之后程序员能够运行相应的编译器,经过指定须要编译的文件的名称就能够把相应的源文件(经过一个复杂的过程)转化为机器码了。编程
![]() |
编译器 |
典型的编译器输出是由包含入口点的名字和地址以及外部调用(到不在这个目标文件中的函数调用)的机器代码所组成的目标文件。一组目标文件,没必要是同一编译器产生,但使用的编译器必需采用一样的输出格式,能够连接在一块儿并生成能够由用户直接执行的可执行程序。后端
![]() |
编译器 |
预处理器:预处理器(preprocessor)做用是经过代入预约义等程序段将源程序补充完整。数组
编译器前端:编译器前端(frontend),前端主要负责解析(parse)输入的源程序,由词法分析器和语法分析器协同工做。词法分析器负责把源程序中的‘单词’(Token)找出来,语法分析器把这些分散的单词按预先定义好的语法组装成有意义的表达式,语句 ,函数等等。 例如“a = b + c;”前端词法分析器看到的是“a = b ; + c;”,语法分析器按定义的语法,先把他们组装成表达式“b + c”,再组装成“a = b + c”的语句。 前端还负责语义(semantic checking)的检查,例如检测参与运算的变量是不是同一类型的,简单的错误处理。最终的结果经常是一个抽象的语法树(abstract syntax tree,或 AST),这样后端能够在此基础上进一步优化,处理。缓存
编译器后端:编译器后端(backend)编译器后端主要负责分析,优化中间代码(Intermediate representation)以及生成机器代码(Code Generation)。网络
编译器分析,优化,变型均可以分红两大类: 函数内(intraprocedural)仍是函数之间(interprocedural)进行。很明显,函数间的分析,优化更准确,但须要更长的时间来完成。对于函数内的优化,有能够根据优化施加的范围分为,全局的(global)和局部的(local)。其中全局的优化是指该优化须要使用到全局的数据流和控制流信息。而局部的优化是指指导优化的信息来自基本快。
![]() |
编译器 |
常见的编译分析有函数调用树(call tree),控制流程图(Control flow graph),以及在此基础上的 变量定义-使用,使用-定义链(define-use/use-define or u-d/d-u chain),变量别名分析(alias analysis),指针分析(pointer analysis),数据依赖分析(data dependence analysis)等。
程序分析结果是编译器优化(compiler optimization)和程序变形(compiler transformation)的前提条件。常见的优化和变新有:函数内嵌(inlining),无用代码删除(Dead code elimination),标准化循环结构(loop normalization),循环体展开(loop unrolling),循环体合并,分裂(loop fusion,loop fission),数组填充(array padding),等等。 优化和变形的目的是减小代码的长度,提升内存(memory),缓存(cache)的使用率,减小读写磁盘,访问网络数据的频率。更高级的优化甚至能够把序列化的代码(serial code)变成并行运算,多线程的代码(parallelized,multi-threaded code)。
机器代码的生成是优化变型后的中间代码转换成机器指令的过程。现代编译器主要采用生成汇编代码(assembly code)的策略,而不直接生成二进制的目标代码(binary object code)。即便在代码生成阶段,高级编译器仍然要作不少分析,优化,变形的工做。例如如何分配寄存器(register allocatioin),如何选择合适的机器指令(instruction selection),如何合并几句代码成一句等等。
![]() |
编译器 |
有限状态自动机(Finite Automaton)和正则表达式(Regular Expression)同上下文无关文法紧密相关,它们与Chomsky的3型文法相对应。对它们的研究与Chomsky的研究几乎同时开始,而且引出了表示程序设计语言的单词的符号方式。
人们接着又深化了生成有效目标代码的方法,这就是最初的编译器,它们被一直使用至今。人们一般将其称为优化技术(Optimization Technique),但因其从未真正地获得过被优化了的目标代码而仅仅改进了它的有效性,所以实际上应称做代码改进技术(Code Improvement Technique)。
当分析问题变得好懂起来时,人们就在开发程序上花费了很大的功夫来研究这一部分的编译器自动构造。这些程序最初被称为编译器的编译器(Compiler-compiler),但更确切地应称为分析程序生成器(Parser Generator),这是由于它们仅仅可以自动处理编译的一部分。这些程序中最著名的是Yacc(Yet Another Compiler-compiler),它是由Steve Johnson在1975年为Unix系统编写的。相似的,有限状态自动机的研究也发展了一种称为扫描程序生成器(Scanner Generator)的工具,Lex(与Yacc同时,由Mike Lesk为Unix系统开发)是这其中的佼佼者。
在70年代后期和80年代早期,大量的项目都贯注于编译器其它部分的生成自动化,这其中就包括了代码生成。这些尝试并未取得多少成功,这大概是由于操做太复杂而人们又对其不甚了解。
编译器设计发展包括:首先,编译器包括了更加复杂算法的应用程序它用于推断或简化程序中的信息;这又与更为复杂的程序设计语言的发展结合在一块儿。其中典型的有用于函数语言编译的Hindley-Milner类型检查的统一算法。其次,编译器已愈来愈成为基于窗口的交互开发环境(Interactive Development Environment,IDE)的一部分,它包括了编辑器、链接程序、调试程序以及项目管理程序。这样的IDE标准并无多少,可是对标准的窗口环境进行开发已成为方向。另外一方面,尽管近年来在编译原理领域进行了大量的研究,可是基本的编译器设计原理在近20年中都没有多大的改变,它如今正迅速地成为计算机科学课程中的中心环节。
在90年代,做为GNU项目或其它开放源代码项目的一部分,许多免费编译器和编译器开发工具被开发出来。这些工具可用来编译全部的计算机程序语言。它们中的一些项目被认为是高质量的,并且对现代编译理论感性趣的人能够很容易的获得它们的免费源代码。
大约在1999年,SGI公布了他们的一个工业化的并行化优化编译器Pro64的源代码,后被全世界多个编译器研究小组用来作研究平台,并命名为Open64。Open64的设计结构好,分析优化全面,是编译器高级研究的理想平台。
![]() |
编译器 |
而后进行语义分析,就是把各个由语法分析分析出的语法单元的意义搞清楚。
最后生成的是目标文件,也称为obj文件。
再通过连接器的连接就能够生成最后的可执行代码了。
有些时候须要把多个文件产生的目标文件进行连接,产生最后的代码。这一过程称为交叉连接。