1.1语言处理器
编译器和解释器
- 第一类:编译器
- 将某一种语言(源语言)程序,发一次一个等价的、用另一种语言(目标语言)编写的程序。
- 如果目标语言是一个可执行的机器语言程序,那么它就可以被用户调用,处理输入并产生输出。
- 效率高
- 第二类:解释器
- 不通过翻译的方式生成目标程序。
- 解释器利用用户提供的输入执行源程序中指定的操作。
- 效率低
- 第三类:混合结构
- java源程序先被编译成一个字节码(bytecode)的中间形式。
- 由一个虚拟机对字节码加以解释执行。
- 及时编译器(just in time) 将字节码翻译成机器语言,加快执行。
1.2 编译器结构
- 词法分析(lexical analysis):读入源程序的字符流,将他们组成有意义的词素(lexeme)的序列。
- 每个词素,分析器产生如下形式的词法单元(taken):<token-name,attribute-value>
- token-name:抽象符号
- attribute-value:符号表中的条目
- position = initial + rate *60 | -- <id,1><=><id,2><+><id,3><><60>
- 词素-- position:<id,1>
- 词素-- =:<=>
- 词素-- initial:<id,2>
- 词素-- +:<+>
- 词素-- rate:<id,3>
- 词素-- :<>
- 词素-- 60:<60>
- 每个词素,分析器产生如下形式的词法单元(taken):<token-name,attribute-value>
- 语法分析(syntax analysis):将词法单元创建一个语法树(syntax tree)
- 语义分析(semantic analyzer):检测源程序和语言定义的语义是否一致
- 类型检测(type checking):
- 自动类型转换(coercion),动态类型
- 中间代码生成: 将语法树编译成一个明确的低级或累积起语言的中间表示。
- 代码优化:将机器无关的代码优化,以便生成更好的目标代码。
- 代码生成:以中间表示形式作为输入,并把它映射到目标语言。
- 如果目标语言是机器代码,那么就必须为程序使用的每个变量选择寄存器或内存位置,中间指令被翻译成为能够完成相同任务的机器指令序列。
- 合理分配寄存器以存放变量的值
- 符号表管理:
- 符号表数据结构为每个变量名字创建一个记录条目,记录的字段是名字的各个属性。
- 允许编译器迅速查找到每个名字的记录,冰箱记录中快速存放获取记录的数据。
- 符号表数据结构为每个变量名字创建一个记录条目,记录的字段是名字的各个属性。
- 将多个步骤组合:
- 前端和后端
- 前端步骤:词法分析、语法分析、语义分析,中间代码生成
- 代码优化可选
- 后端步骤:代码生成
- 有些编译器集合是围绕一组静心设计的中间表示形式而创建的,这些中间表示形式使得我们可以把特定语言的前端和特点目标机的后端相节后。
- 不同的前端和某个目标机的后端结合起来,为不同的源语言建立该目标机器上的编译器
- 一个前端和不同后端结合,建立针对不同目标机器的编译器
- 前端和后端
- 编译器构造工具:
- 语法分析器的生成器:可以根据一个程序设计语言的语法描述自动生成语法分析器。
- 扫描的生成器:可以根据一个语言的语法单元的正则表达式描述生成词法分析器。
- 语法知道的翻译引擎:可以生成一组用于变量分析树并生成中间代码的例程。
- 代码生成器的生成器:依据一组关于如何把中间语言的每个运算翻译成目标机上的机器语言的规则,生成一个代码生成器。
- 数据流分析引擎:可以帮助手机数据流信息,即程序中的值如何从程序的一个部分传递到另一部分。数据流分析是代码优化的重要部分
- 编译器构造工具:提供了可用于构造编译器的不同阶段的例程的完整集合。数据流分析是代码优化的重要部分
1.3 编译器结构
语言的发展
- 按代划分
- 第一代:机器预研
- 第二代:汇编语言
- 第三代:Fortran、Cobol、Lisp、C、C++、C#、Java
- 第四代:特定应用设计的语言
- 生成报告:NOMAD
- 数据库查询:SQL
- 文本排版:Postscript
- 第五代:基于逻辑和约束。Prolog和OPS5
- 完成计算机任务:
- 强制式(imperative):C、C++、C#、Java
- 声明式(declarative):ML、Haskell、Prolog
1.4 编译器的相关科学
- 编译器设计和实现的建模:如何设计正确的数学模型和选择正确算法的研究。
- 代码优化科学:
- 优化必须是正确的,也就是说,不能改变编译器程序的含义
- 优化必须嫩巩固改善很多程序的性能
- 优化所需的时间必须保持在合理的范围内
- 所需的工程方面的工作必须是可管理的
1.5 编译技术的应用
- 高级程序设计语言的实现:
- 高级语言用低层次内存寄存器控制,很可能会损失性能,特别是目标机器不同,编译后的目标程序效率更低
- 针对计算机体系结构的优化:
- 并行性:所有现在微处理器都采用了指令级并行性,多处理器日益流行
- 内存层次结构:如果一个程序的大部分内存访问都能够由层次结构中最快的满足,那么程序的平均内存访问时间就会降低。
- 新计算机体系结构的设计
- RISC(精简指令集计算机):Reduced Instruction-Set Computer
- CISC(复杂指令集计算机):Complex Instruction-Set computer
- 专用体系结构:数据量集群、向量机、VLIW(非常长指令字)机器、SIMD(单指令,多数据)处理器阵列、心动阵列(systolic array)、共享内存的多处理器、分布式内存的多处理器。
- 程序翻译
- 二进制翻译:把一个机器的二进制代码翻译成另一个机器的二进制代码
- 硬件合成:Verilog和VHDL
- 数据查询解释器:SQL
- 编译后模拟:编译后的模拟运行科比基于解释器的方法快借个数量级。(Verilog和VHDL)
- 软件生产工具:
- 数据流分析:可以在所有可能的执行路径上找错误,而不是像程序测试的时候所在的那样,仅仅在那些由输入数据组合执行的路径上找错误。
- 类型检查:用于捕捉陈翔的不一致性
- 边界检查:检查数据不越界
- 内存管理工具:垃圾辉煌搜集只是在效率和易编译及软件可靠性之间进行折衷处理的例子。
1.6 程序设计预研基础
- 静态和动态区别:
- 静态策略(编译时策略):一个语言使用的策略支持编译器静态决定某个问题 。
- 静态作用域(static scope):变量类型直接声明 。C/Java
- 动态策略(运行时策略):一个只允许在运行程序的时候做出决定的策略 。
- 动态作用域(static scope):当程序运行时,x可以指向x的几个声明中的一个。
- 静态策略(编译时策略):一个语言使用的策略支持编译器静态决定某个问题 。
- 环境与状态(作用域)
- 环境:从一个名字 (变量名) 到存储位置的映射。从名字到变量名的映射,C语言中的左值
- 状态:从内存位置到它们值的映射。C语言:把左值映射到它们的相应的右值
- 环境的改变要遵守语言的作用域规则。
- 当f() 运行时,环境相应的调整,是的名字i指向哪个为局部变量
- 环境的改变要遵守语言的作用域规则。
...
int i; /**全局i*/
...
void f(..){
int i; /**局部i*/
...
i = 3; /**对局部i的使用*/
...
}
...
x = i+1; /**对全局i的使用*/
- 静态作用域和块结构
- 块:C使用 {和} 来界定一个快,由用 begin 和 end 。
- 块是一种语句
- 一个块包含了一个声明的序列,然后在跟着一个语句序列。
- C语言的静态作用域:
- 一个C程序由一个顶层的变量和函数声明的序列组成。
- 函数内部可以声明变量,变量包括局部变量和参数,每个这样的声明作用域呗限制在它们所出现的那个函数内。
- 名字x的一个顶层声明的作用域包括其后的所有程序,做个一个函数中也有一个x的声明,那么函数中的x不再顶层声明的作用域中。
- 块:C使用 {和} 来界定一个快,由用 begin 和 end 。
- 显示访问控制:
- C++/JAVA:public、private、protected
- 动态作用域:
- 对一个名字x的变量的使用,指向的最近被调用且生命没有终止。
- C预处理器中的宏扩展
- 如下伪码:当b()执行的时候,会用(X+1) 替换掉a
- 面向对象编程中的方法解析
- 动态作用域解析对多台过程是必不可少的。多态:指对于同一个名字根据参数类型具有两个或多个定义的过程
- C预处理器中的宏扩展
- 对一个名字x的变量的使用,指向的最近被调用且生命没有终止。
#define a (x+1)
int x = 2;
void b() {int x =1 ; printf("%d\n",a) ;}
void c() {print("%d",a);}
void main() {b() ; c();}
- 参数传递机制:实参和形参关联。
- 值传递、引用传递
- 值调用(call-by-value):调用过程所做的所有关于形式参数的计算都局限于这个过程,相应的实参不会呗改变。可以通过传入一个指针,来改变实参。
- 引用调用(call-by-reference):实参的地址作为相应的形参值传递给了调用者。
- 别名:多个变量都指向同一个位置