交叉编译器
百科名片
编辑本段基本简介
高级
计算机语言便于人编写,阅读,维护。低阶
机器语言是计算机能直接解读、运行的。
编译器将
源程序(Source program)作为输入,翻译产生使用目标语言(Target language)的等价程序。
源代码一般为高级语言 (High-level language), 如 Pascal、C、C++、C#、Java等,而目标语言则是汇编语言或目标机器的
目标代码(Object code),有时也称作机器代码(Machine code)。
编辑本段工作原理
编译是从
源代码(通常为高阶语言)到能直接被计算机或
虚拟机执行的
目标代码(通常为低阶语言或
机器语言)的翻译过程。然而,也存在从低阶语言到高阶语言的
编译器,这类编译器中用来从由高阶语言生成的低阶语言代码重新生成高阶语言代码的又被叫做反编译器。也有从一种高阶语言生成另一种高阶语言的
编译器,或者生成一种需要进一步处理的的
中间代码的编译器(又叫级联)。
典型的
编译器输出是由包含
入口点的名字和地址, 以及外部调用(到不在这个
目标文件中的
函数调用)的机器代码所组成的目标文件。一组
目标文件,不必是同一
编译器产生,但使用的编译器必需采用同样的输出格式,可以链接在一起并生成可以由用户直接执行的可执行程序。
编辑本段分类说明
编译器可以生成用来在与编译器本身所在的计算机和操作系统(平台)相同的环境下运行的
目标代码,这种编译器又叫做“本地”编译器。另外,
编译器也可以生成用来在其它平台上运行的
目标代码,这种编译器又叫做
交叉编译器。
交叉编译器在生成新的硬件平台时非常有用。“源码到源码
编译器”是指用一种高阶语言作为输入,输出也是高阶语言的编译器。例如: 自动并行化
编译器经常采用一种高阶语言作为输入,转换其中的代码,并用并行代码注释对它进行注释(如OpenMP)或者用语言构造进行注释(如FORTRAN的DOALL指令)。
预处理器(preprocessor)
编译器前端(frontend)
前端主要负责解析(parse)输入的
源代码,由
语法分析器和语意分析器
协同工作。
语法分析器负责把
源代码中的‘单词’(Token)找出来,语意分析器把这些分散的单词按预先定义好的语法组装成有意义的
表达式,语句 ,函数等等。 例如“a = b + c;”前端
语法分析器看到的是“a, =, b , +, c;”,语意分析器按定义的语法,先把他们组装成
表达式“b + c”,再组装成“a = b + c”的语句。 前端还负责语义(semantic checking)的检查,例如检测参与运算的
变量是否是同一类型的,简单的错误处理。最终的结果常常是一个抽象的语法树(abstract syntax tree,或 AST),这样后端可以在此基础上进一步优化,处理。
编译器后端(backend)
一般说来所有的
编译器分析,优化,变型都可以分成两大类: 函数内(intraprocedural)还是函数之间(interprocedural)进行。很明显,函数间的分析,优化更准确,但需要更长的时间来完成。
编辑本段代码分析
编译器分析(compiler analysis)的对象是前端生成并传递过来的中间代码,现代的优化型编译器(optimizing compiler)常常用好几种层次的中间代码来表示程序,高层的中间代码(high levelIR)接近输入的
源程序的格式,与输入语言相关(language dependent),包含更多的全局性的信息,和源程序的结构;中层的中间代码(middle level IR)与输入语言无关,低层的中间代码(Low level IR)与机器语言类似。 不同的分析,优化发生在最适合的那一层中间代码上。
常见的编译分析有函数调用树(call tree),控制流程图(Control flow graph),以及在此基础上的 变量定义-使用,使用-定义链(define-use/use-define or u-d/d-u chain),变量别名分析(alias analysis),指针分析(pointer analysis),数据依赖分析(data dependenceanalysis)等。
程序分析结果是编译器优化(compileroptimization)和程序变形(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),如何合并几句代码成一句等等。
编辑本段工作方法
最后生成的是目标文件,也称为obj文件。
有些时候需要把多个文件产生的
目标文件进行链接,产生最后的代码。这一过程称为交叉链接。
编辑本段语言对比
许多人将高阶程序语言分为两类:
编译型语言和 直译型语言 。然而,实际上,这些语言中的大多数既可用编译型实现也可用直译型实现,分类实际上反映的是那种语言常见的实现方式。(但是,某些直译型语言,很难用编译型实现。比如那些允许 在线代码更改 的直译型语言。)
编辑本段发展历史
20世纪50年代,IBM的John Backus带领一个研究小组对FORTRAN语言及其
编译器进行开发。但由于当时人们对编译理论了解不多,开发工作变得既复杂又艰苦。与此同时,Noam Chomsky开始了他对自然语言结构的研究。他的发现最终使得
编译器的结构异常简单,甚至还带有了一些自动化。Chomsky的研究导致了根据语言文法的难易程度以及识别它们所需要的算法来对语言分类。正如所称的Chomsky架构(Chomsky Hierarchy),它包括了文法的四个层次:0型文法、1型文法、2型文法和3型文法,且其中的每一个都是其前者的特殊情况。2型文法(或上下文无关文法)被证明是
程序设计语言中最有用的,而且今天它已代表着程序设计语言结构的标准方式。分析问题(parsing problem,用于上下文无关文法识别的有效算法)的研究是在60年代和70年代,它相当完善的解决了这个问题。它已是
编译原理中的一个标准部分。
有限状态自动机(Finite Automation)和
正则表达式(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系统开发)是这其中的佼佼者。
编译器设计最近的发展包括:首先,编译器包括了更加复杂算法的应用程序它用于推断或简化程序中的信息;这又与更为复杂的
程序设计语言的发展结合在一起。其中典型的有用于函数语言编译的Hindley-Milner类型检查的统一算法。其次,
编译器已越来越成为基于窗口的交互开发环境(Interactive Development Environment,IDE)的一部分,它包括了编辑器、
连接程序、
调试程序以及项目管理程序。这样的IDE标准并没有多少,但是对标准的窗口环境进行开发已成为方向。另一方面,尽管在
编译原理领域进行了大量的研究,但是基本的
编译器设计原理在近20年中都没有多大的改变,它正迅速地成为计算机科学课程中的中心环节。
在20世纪90年代,作为GNU项目或其它开放源代码项目标一部分,许多免费
编译器和编译器开发工具被开发出来。这些工具可用来编译所有的计算机程序语言。它们中的一些项目被认为是高质量的,而且对现代编译理论感兴趣的人可以很容易的得到它们的免费源代码。
大约在1999年,SGI公布了他们的一个工业化的并行化优化
编译器Pro64的
源代码,后被全世界多个编译器研究小组用来做研究平台,并命名为
Open64。
Open64的设计结构好,分析优化全面,是
编译器高级研究的理想平台。
编辑本段交叉编译
在一种计算机环境中运行的
编译程序,能编译出在另外一种环境下运行的代码,我们就称这种
编译器支持
交叉编译。这个编译过程就叫
交叉编译。简单地说,就是在一个平台上生成另一个平台上的
可执行代码。这里需要注意的是所谓平台,实际上包含两个概念:
体系结构(Architecture)、操作系统(Operating System)。同一个
体系结构可以运行不同的操作系统;同样,同一个操作系统也可以在不同的体系结构上运行。举例来说,我们常说的x86 Linux平台实际上是Intel x86
体系结构和Linux for x86操作系统的统称;而x86 WinNT平台实际上是Intel x86体系结构和Windows NT for x86操作系统的简称。
有时是因为目的平台上不允许或不能够安装我们所需要的
编译器,而我们又需要这个编译器的某些特征;有时是因为目的平台上的资源贫乏,无法运行我们所需要编译器;有时又是因为目的平台还没有建立,连操作系统都没有,根本谈不上运行什么编译器。
交叉编译这个概念的出现和流行是和
嵌入式系统的广泛发展同步的。我们常用的
计算机软件,都需要通过编译的方式,把使用高级
计算机语言编写的代码(比如C代码)编译(compile)成计算机可以识别和执行的
二进制代码。比如,我们在Windows平台上,可使用Visual C++
开发环境,编写程序并编译成可执行程序。这种方式下,我们使用PC平台上的Windows工具开发针对Windows本身的可执行程序,这种编译过程称为native compilation,中文可理解为本机编译。然而,在进行
嵌入式系统的开发时,运行程序的目标平台通常具有有限的
存储空间和运算能力,比如常见的 ARM 平台,其一般的
静态存储空间大概是16到32MB,而CPU的主频大概在100MHz到500MHz之间。这种情况下,在ARM平台上进行本机编译就不太可能了,这是因为一般的编译工具链(compilation tool chain)需要很大的
存储空间,并需要很强的CPU运算能力。为了解决这个问题,
交叉编译工具就应运而生了。通过
交叉编译工具,我们就可以在CPU能力很强、
存储空间足够的主机平台上(比如PC上)编译出针对其他平台的可执行程序。
要进行
交叉编译,我们需要在主机平台上安装对应的交叉编译工具链(cross compilation tool chain),然后用这个交叉编译工具链编译我们的
源代码,最终生成可在目标平台上运行的代码。
编辑本段常见例子
交叉编译
4、在Windows系统上,利用Keil Uvison工具,开发出运行在89C51单片机上的程序。
5、在Windows系统上,利用CodeWarrior IDE工具,开发出运行在Freescale XS128单片机上的程序。