编译器的编译基本过程

编译器最基本的功能就是把高级语言编写的代码转化为机器指令(就是01串),从这个角度来说它本质上是个转换过程。经典的编译过程主要包括:

  1. 词法分析(Lexical Analysis)

词法分析就是从输入代码中识别出各种记号(token),例如对于C语言我们就需要知道if,else等是语言的关键字,myvar是个标识,而123myvar不能被识别为一个标识。负责实现词法分析的模块有时也称为scanner

词法分析的关键当然是语言定义的规则了,比如哪些是关键词,哪些是合法的标识等,这些规则一般是通过正则表达式(RERegular
Expression
)来给出,运行时从输入缓冲区中读入一部分,然后看和哪个RE匹配就知道它到底是个什么token

下一个问题就是正则表达式的匹配过程如何实现,经典理论对此都会提到有限状态机(FSMFinite
State Machine
)。关于FSM在可行性计算里一般都会有不少篇幅分析,在编译里谈到的FSMRE主要是如何从输入的构成出对应的FSM。构造的过程一般分为三个步骤,

  • 根据Thompson构造法从RE构造出对应的非确定性有限状态机(NFANon-deterministic
    Automata
    );
  • 经过不断计算epison-闭包(也成为可到达集)构造出确定性有限状态机(DFAdeterministic
    Automata
    );
  • DFA最小化,方法是增量式地合并不可区分(对于同一输入的下一个状态都一样)的两个状态。
    1. 语法分析

语法分析的输入是一连串的token(词法分析的输出),根据语言的语法规则不断解析最后得到一棵抽象语法树(AST,Abstract
Syntax Tree
),负责语法分析模块通常也被叫做Parser。在词法分析中,我们经常使用正则表达式来表示语言所接受的token的规则,类似的,在语法分析中,我们使用文法(Grammar)来表示语言的语法规则,这也早期计算机语言设计中的研究热点(同样也是大学里学习编译时最容易让人头晕的东西)。

编译里常说的文法指的是一种上下文无关文法(Context-Free Grammar),简单地说文法里包含终结符(terminal,就是26个字符、数字等等)、非终结符(nonterminal,实际是一种抽象)和产生式(production)。上下文无关文法要求每个产生式的左边必须恰好是一个非终结符,而右边是0个或多个终结符与非终结符的组合,最后整个文法还必须有一个起始符(某个终结符)。文法里还有些很重要的基本概念,例如推导(derivation)、归约(reduction)、二义性(ambiguity)、最左推导等等。

文法中最重要的基本概念是FIRST集和FOLLOW集的构造。根据这两个集合就可以很容易构造出一个预测分析表,每个行的名字是一个非终结符,每个列的名字是一个终结符,如果每个表格内没有两个以上的项,那么说明是一个LL(1)文法(Left-to-right
parse
Leftmost-derivation1-symbol
lookhead
),简单地说就是向右边看一个符号就能确定下一步动作。当原文法不是LL(1)文法时,可以尝试通过消除左递归(Eliminate
Left Recursion
)和提取左因子(Left Factoring)对原文法进行变形得到等价的LL(1)文法。

3、语义分析(Sematic Analysis)

语义分析包括一些经典的问题。

(1)类型检查(Type Checking),例如在语法树上a+b看起来是没问题的,因为ab都是合法的变量名,并且语法中支持变量间+这种操作。但是可能a是一个字符串,而b是一个浮点数,这两者之间的+操作就不符合语义规范了,这种问题在这个阶段都会被找出来。

(2)符号管理,最经典的问题就是如何管理变量(变量的名字,类型,变量的作用域(scope)等),在分析代码时,符号管理肯定是被频繁的搜索,因此它通常会使用hash来组织。

4、中间代码(IR, intermediate Representation)生成

IR是非常非常重要的,它被引入的初衷是提高编译器开发的效率。IR是编译过程的一个汇聚点,在IR之前我们通常都认为是编译的前端,而IR之后是编译的后端,这样当编译器需要多支持一种高级语言时主要工作就是提供一个前端,而当需要移植到一种新的平台上时主要工作就是提供对应的后端。关于IR的表示典型的有三地址码。IR生成的过程就是将一棵抽象语法树(这是编译器前端对源代码的理解和抽象)变成一串IR定义的代码(IR指令种类简单,这便于优化)。这个转换过程都是比较固定的套路,例如if-elsewhile/for等基本结构如何转都是一个固定的套路。

5、编译优化

这一部分是现代编译器最核心所在,主要有两类,一类是通用的优化手段,比如死代码删除、循环不变量外提、强度削弱等,另一类就是体系结构相关的,说白了就是某种体系结构针对某类应用提供了特殊指令,例如intelMMXSSE2等等。为支持优化工作的开展,我们首先需要能够比较方便的描述代码。最基本的当然是一条指令,但是这个太细微,于是往上抽象出基本块(Basic
Block
),这个基本上是所有优化开展必备的工作,然后多个基本块还可以构成一个超级块(region)。此外,经典的方法还包括控制流分析和数据流分析,这里常用的包括d-u链等。最后一个经典的topic就是寄存器分配。

6、目标代码生成

这里直接和具体平台相关,这里的平台同时包括软件和硬件,例如哪种目标文件格式(ELFPE),哪种平台(指令集)。不过现在编译器一般生成的是字符形式的汇编文件,所以前面一个问题基本不大,主要影响在后者。

时间: 2024-10-09 06:46:35

编译器的编译基本过程的相关文章

Android系统开发(1)——GCC编译器的编译和安装过程

GCC编译器介绍 GCC编译器(GNG C Compiler)是GNU项目中符合ANSI C标准的编译系统,能够编译C  C++  Object C等语言编写的程序,同时GCC也是一个交叉编译器,特别使用于不同平台的嵌入式开发. GNU: GNU是"GNU is Not Unix"的递归缩写,1984年,史托曼开始GNU项目,这个项目的目的是创建一个自由,开放的Unix操作系统(Free UNIX),刚开始史托曼参考UNIX上面的软件,开发出功能类似的软件,在开发期间并没有看其他软件的

转载 关于编译器的编译过程

调试的问题出现这个问题还蛮奇怪的,a.h需包含statistic.h, main.c需包含a.h和statistic.h,导致了statistic.h重复包含,hsdm_queue_ctr重复定义. 关于重复包含和重复定义的问题,可根据下面的分析得到认识. 为什么用了宏名字定义检测头文件的重复包含可还是有重复定义的错误? 我在一个头文件中定义了一个函数F, 整个头文件用:#ifndef _XXX#define _XXX...#endif括起来以避免重复包含. 然后有两个Cpp文件都包含了这个头文

C++模版的编译链接过程

模版(template)设计的初衷,是设计一种自动实例化机制,不需要使用者参与,编译器可根据使用者提供的模版参数再套用类的定义来实例化.所谓实例化,除了包含对于程序变量的实例化,即开辟空间并设置某些变量的初值(构造函数)以及指针(如vptr)以及其他支持(virtual base class offset),还有对于函数的实例化,即根据函数的定义生成机器指令,并在函数调用处提供函数的入口地址.简单来看,普通类和模版类的区别,在于普通类实例化时有较为固定的空间开销(除非类似new string(n

程序的编译链接过程

还是从HelloWorld开始说吧... #include <stdio.h> int main(int argc, char* argv[]) { printf("Hello World!\n"); return 0; } 从源文件Hello.cpp编译链接成Hello.exe,需要经历如下步骤: 可使用以下命令,直接从源文件生成可执行文件 linux: gcc -lstdc++ Hello.cpp -o Hello.out // 要带上lstdc参数,否则会报undef

C/C++编译链接过程详解

有些人写C/C++(以下假定为C++)程序,对unresolved external link或者duplicated external simbol的错误信息不知所措(因为这样的错误信息不能定位到某一行).或者对语言的一些部分不知道为什么要(或者不要)这样那样设计.了解本文之后,或许会有一些答案. 首先看看我们是如何写一个程序的.如果你在使用某种IDE(Visual Studio,Elicpse,Dev C++等),你可能不会发现程序是如何组织起来的(很多人因此而反对初学者使用IDE).因为使

Notepad++根据语言类型自动选择对应编译器来编译代码

Notepad++的NppExec插件可以调用好多编译器来编译代码. 比如编译Java: NPP_SAVE javac "$(FULL_CURRENT_PATH)" java -cp "$(CURRENT_DIRECTORY)" "$(NAME_PART)" 保存为:Run Java 编译Python: NPP_SAVE python "$(FULL_CURRENT_PATH)" 保存为:Run Python 然后就是自动切换

windows Notepad++ 上配置 vs 编译器 , 编译并运行

windows 中 配置 vs编译器 在Linux下,Kris是倾向于在终端中使用gcc和g++来编译C/C++的,在Windows下相信很多人都是选择臃肿的Visual Studio,我亦不免如此.但是,我希望在Windows下也能像Linux下一样简洁编程,于是开始了我的Windows下的C/C++命令行编译环境打造之路. 几乎没有人会否认集成开发环境 (IDE)(例如,Visual Studio,NetBeans,Eclipse)所提供的能使编程工作变得相当简单的诸多功能.但,还是说说命令

c语言编译执行过程

<h4>认识C编译执行过程</h4>认识C编译执行过程,是C学习的开端.简单说C语言从编码编译到执行要经历一下过程: C源代码编译---->形成目标代码,目标代码是在目标机器上运行的代码.连接---->将目标代码与C函数库相连接,并将源程序所用的库代码与目标代码合并,并形成最终可执行的二进制机器代码(程序).执行----->在特定的机器环境下运行C程序. 如果用一个图 来表示: <a href="http://www.emacsvi.com/wp-

C程序编译执行过程

C程序编译执行过程 认识C编译执行过程,是C学习的开端. 简单说C语言从编码编译到执行要经历一下过程: C源代码 编译---->形成目标代码,目标代码是在目标机器上运行的代码. 连接---->将目标代码与C函数库相连接,并将源程序所用的库代码与目标代码合并,并形成最终可执行的二进制机器代码(程序). 执行----->在特定的机器环境下运行C程序. 如果用一个图 来表示: 以上过程仅仅是个大概,详细的过程相当复杂,下面这篇文章写得很详细,从中受益很多: 原文来自:http://www.vc