编译原理课件Chapter_7
合集下载
编译原理-刘善梅-第7章 语义分析和中间代码产生6 127页PPT文档
第七章 语义分析和中间代码产生
中间语言 赋值语句的翻译 布尔表达式的翻译 控制语句的翻译 过程调用的处理
赋值语句的翻译
1.简单算术表达式及赋值语句
id:=E 对表达式E求值并置于变量T中 id.place=T
从赋值语句生成三地址代码的S-属性文法
非终结符号S有综合属性S.code,它代表 赋值语句S的三地址代码。
三元式 四元式 间接三元式
7.1.1 后缀式
后缀式表示法:Lukasiewicz发明的一种表示 表达式的方法,又称逆波兰表示法。
一个表达式E的后缀形式可以如下定义:
1. 如果E是一个变量或常量,则E的后缀式是E 自身。
2. 如果E是E1 op E2形式的表达式,其中op是任 何二元操作符,则E的后缀式为E1 E2 op,其 中E1 和E2 分别为E1 和E2的后缀式。
a+a*(b-c)+(b-c)*d的DAG
+
+
*
a
-
* d
b
c
a有两个父结点(+,*); 表达式b-c也有两个父结点;
a:=b*(-c)+b*(-c)的图表示法
assign
assign
a
+
a
*
*
b uminus b uminus
+
*
b
uminus
c
c
抽象语法树
c DAG
a:=b*(-c)+b*(-c)的抽象语法树对应的代码
T4:=b*T3
T5:=T2+T4
a
+
a:=T5
*
b
编译原理ppt第七章
1 目标
提升生成的目标代码的性能和效率。
2 优化方法
通过代码转换和代码生成技术进行优化,如常量传播、死代码删除等。
3 效果
优化后的代码可以提高程序的执行效率,减少资源消耗。
图片来源
编译系统组成
了解编译系统的组成对于理解整个 编译过程非常重要。
词法分析器
词法分析器将源代码分解成一个个 词法单元,如标识符、关键字、运 算符。
编译原理ppt第七章
通过这一章的学习,你将深入了解编译系统的组成和各个阶段的作用,包括 词法分析器、语法分析器、语义分析器、中间代码生成以及编译器的优化。
课程概述
1 理解编译原理的重要性 2 学习编译原理的实际
应用
编译原理是计算机科学的基
3 掌握编译器的工作原理
通过学习编译器的各个阶段,
础,掌握它有助于开发高效
了解编译系统可以更好地理
你将能够设计和实现自己的
且可靠的软件。
解代码运行的过程,进而改
编译器。
进代码的性能和可维护性。
编译系统概述
定义
编译系统是一种将源代码转换 为目标代码的软件工具。
组成
编译系统由多个阶段组成,每 个阶段负责不同的任务。
功能
编译系统可以进行词法分析、 语法分析、语义分析等操作, 最终生成可执行的目标代码。
编译器的优化
编译器的优化可以提高代码的性能 和效率。
编译系统的阶段
1
词法分析器
将源代码转换为一系列词法单元,如标识符、
语法分析器
2
关键字、运算符等。
根据语法规则分析词法单元的组合,生成抽象语法。3语义分析器
对抽象语法树进行语义检查,如类型匹配、
中间代码生成
提升生成的目标代码的性能和效率。
2 优化方法
通过代码转换和代码生成技术进行优化,如常量传播、死代码删除等。
3 效果
优化后的代码可以提高程序的执行效率,减少资源消耗。
图片来源
编译系统组成
了解编译系统的组成对于理解整个 编译过程非常重要。
词法分析器
词法分析器将源代码分解成一个个 词法单元,如标识符、关键字、运 算符。
编译原理ppt第七章
通过这一章的学习,你将深入了解编译系统的组成和各个阶段的作用,包括 词法分析器、语法分析器、语义分析器、中间代码生成以及编译器的优化。
课程概述
1 理解编译原理的重要性 2 学习编译原理的实际
应用
编译原理是计算机科学的基
3 掌握编译器的工作原理
通过学习编译器的各个阶段,
础,掌握它有助于开发高效
了解编译系统可以更好地理
你将能够设计和实现自己的
且可靠的软件。
解代码运行的过程,进而改
编译器。
进代码的性能和可维护性。
编译系统概述
定义
编译系统是一种将源代码转换 为目标代码的软件工具。
组成
编译系统由多个阶段组成,每 个阶段负责不同的任务。
功能
编译系统可以进行词法分析、 语法分析、语义分析等操作, 最终生成可执行的目标代码。
编译器的优化
编译器的优化可以提高代码的性能 和效率。
编译系统的阶段
1
词法分析器
将源代码转换为一系列词法单元,如标识符、
语法分析器
2
关键字、运算符等。
根据语法规则分析词法单元的组合,生成抽象语法。3语义分析器
对抽象语法树进行语义检查,如类型匹配、
中间代码生成
编译原理课程第7讲
循环优化
循环展开
将循环的次数减少,将循环体内部的 代码移出循环。
循环不变量代码外提
将循环不变量代码移出循环,避免重 复计算。
循环展开和合并
将多个循环合并为一个循环,减少循 环次数。
尾递归优化
将尾递归转换为循环,提高循环的效 率。
06
目标代码生成
目标代码生成概述
01
02
03
目标代码生成是编译过 程的核心环节,其任务 是将中间代码转换成特 定机器上的可执行代码
中间代码的作用
中间代码主要用于优化和 转换,以改善生成的目标 代码的性能和质量。
中间代码的形式
常见的中间代码形式包括 三地址代码、抽象语法树 (Abstract Syntax Tree, AST)等。
三地址代码的生成
三地址代码定义
三地址代码是一种中间代码形式, 由一系列的三元式(操作符、操 作数1、操作数2)组成,表示源 程序中的运算和数据传输。
课程目标
本课程旨在帮助学生掌握编译原理的基本概念、原理和方法 ,理解编译器的构造过程,培养学生的编程思维和解决问题 的能力。
编译器的概念与作用
编译器的概念
编译器是一种将源代码转换成目标代码的软件。源代码通常是高级语 言编写的,而目标代码则是机器可以直接执行的低级语言。
代码优化
编译器可以对源代码进行优化,以提高生成的目标代码的运行效率。
循环不变量计算
将循环中的某些计算移出循环,减少循环 内部的计算量。
循环展开与循环不变量计算的结 合
同时进行循环展开和循环不变量计算,进 一步提高目标代码的执行效率。
05
代码优化
代码优化概述
01
代码优化是编译器的一个重要环节,旨在提高生成代码的执行 效率。
《编译原理》课件
代码生成
编译器可以将高级语言编写的源代码转换成机器语言或低级语言,以便在特定的硬件平台上运行。编 译器还可以生成可执行文件或动态链接库等二进制文件。
编译器在人工智能领域的应用
机器学习编译器
机器学习编译器可以将机器学习模型转换成可执行代码,以便在嵌入式设备或边缘计算 设备上运行。这种编译器可以优化模型的计算性能和内存占用,提高模型的运行效率。
3
缺点
对于某些复杂文法,可能导致大量的无用推导和 状态爆炸。
自底向上的语法分析
分析步骤
从输入符号序列的最后一个符号开始,逐步向上构建语法树,直 到找到与文法中的某个产生式右部匹配的符号串。
优点
可以充分利用已知信息,避免不必要的推导和状态爆炸。
缺点
对于某些复杂文法,可能导致大量的无用归约和状态爆炸。
04
中间代码生成
中间代码生成的定义和任务
定义
中间代码生成是编译器的一个阶段,将源代码转换成中间代码的过程。
任务
将源代码转换成一种中间表示形式,以便进行后续的优化和目标代码生成。
三地址代码的生成
01
三地址代码是一种中间代码形 式,由一系列的三元式组成。
02
三元式的形式为(op, arg1, arg2),表示执行一个操作(op) 并产生一个结果,操作数arg1 和arg2来自寄存器、常数或之 前的计算结果。
语义分析
检查AST是否有语义错误,如类型错 误、未定义的变量等。
中间代码生成
将AST转换为中间代码,通常是三地 址代码。
代码优化
对中间代码进行优化,提高执行效 率。
代码生成
将中间代码转换为机器语言代码, 能够在特定硬件上执行。
编译器的分类
编译器可以将高级语言编写的源代码转换成机器语言或低级语言,以便在特定的硬件平台上运行。编 译器还可以生成可执行文件或动态链接库等二进制文件。
编译器在人工智能领域的应用
机器学习编译器
机器学习编译器可以将机器学习模型转换成可执行代码,以便在嵌入式设备或边缘计算 设备上运行。这种编译器可以优化模型的计算性能和内存占用,提高模型的运行效率。
3
缺点
对于某些复杂文法,可能导致大量的无用推导和 状态爆炸。
自底向上的语法分析
分析步骤
从输入符号序列的最后一个符号开始,逐步向上构建语法树,直 到找到与文法中的某个产生式右部匹配的符号串。
优点
可以充分利用已知信息,避免不必要的推导和状态爆炸。
缺点
对于某些复杂文法,可能导致大量的无用归约和状态爆炸。
04
中间代码生成
中间代码生成的定义和任务
定义
中间代码生成是编译器的一个阶段,将源代码转换成中间代码的过程。
任务
将源代码转换成一种中间表示形式,以便进行后续的优化和目标代码生成。
三地址代码的生成
01
三地址代码是一种中间代码形 式,由一系列的三元式组成。
02
三元式的形式为(op, arg1, arg2),表示执行一个操作(op) 并产生一个结果,操作数arg1 和arg2来自寄存器、常数或之 前的计算结果。
语义分析
检查AST是否有语义错误,如类型错 误、未定义的变量等。
中间代码生成
将AST转换为中间代码,通常是三地 址代码。
代码优化
对中间代码进行优化,提高执行效 率。
代码生成
将中间代码转换为机器语言代码, 能够在特定硬件上执行。
编译器的分类
编译原理课件第7章
P→MD M→ {offset := 0 }
D→D ; D
D→id : T {enter( , T.type, offset );
offset := offset + T.width}
T→integer {T.type := integer; T.width := 4}
T→real {T.type := real; T.width := 8}
D→id : T {enter( , T.type, offset ); offset := offset + T.width}
P
offset=0
D
D
;
offset=8
D
offset=12
id
:
T
id
:
T
real
enter(x,real,0)
2020/2/3
T.type=real T.width=8
• type row = record
• address: integer;
• lexeme: array[1..15] of char
• end;
• var table : array [1..10] of row;
6.如果T是类型表达式,那么pointer(T)也是类型表 达式,表示“指向类型为T的对象的指针”。
integer T.type=integer
T.width=4
enter(i,integer,8)
20
例 x:real; i:integer 的翻译
P{offset:=0}D {offset:=0}D;D {offset:=0}x:T{enter(x,T.type,offset);
编译原理课件Chapter-7
INTEGER X;
(2)
CALL M2( IND + 1 );
END M1;
M2: PBLOCK( INTEGER J );
高层(内层)模块可以引用低层(外层) 模块中的变量,例如在M1中可引用外层模块中 定义的变量 Y。
在 M1 中引用Y时,可通过其 display 区找 到程序块 1 的活动记录基地址,加上 Y 在该数 据区的相对地址就可以求得 y 的绝对地址。
abp
(a) 进入模块1
1 BBLOCK; 2 REAL X, Y; STRING NAME; M1: PBLOCK( INTEGER IND ) ; INTEGER X;
CALL M2( IND + 1 );
3 END M1; M24: PBLOCK( INTEGER J ); BBLOCK; ARRAY INTEGER F( J ); LOGICAL TESTI; … END END M2;
(h)当最外层模块执行完,运行栈恢复到进入模块时 的情况,运行栈空。
main的数据 (空) 进入main函数的情况 OS
abp(0) → (空) 1
BBLOCK; 2 REAL X, Y; STRING NAME; M1: PBLOCK( INTEGER IND ) ; INTEGER X;
CALL M2( IND + 1 );
3 END M1; M24: PBLOCK( INTEGER J ); BBLOCK; ARRAY INTEGER F( J ); LOGICAL TESTI; … END END M2;
CALL M1( X / Y )
END
1 X, Y, NAME
2 M1: (IND); X;
(2)
CALL M2( IND + 1 );
END M1;
M2: PBLOCK( INTEGER J );
高层(内层)模块可以引用低层(外层) 模块中的变量,例如在M1中可引用外层模块中 定义的变量 Y。
在 M1 中引用Y时,可通过其 display 区找 到程序块 1 的活动记录基地址,加上 Y 在该数 据区的相对地址就可以求得 y 的绝对地址。
abp
(a) 进入模块1
1 BBLOCK; 2 REAL X, Y; STRING NAME; M1: PBLOCK( INTEGER IND ) ; INTEGER X;
CALL M2( IND + 1 );
3 END M1; M24: PBLOCK( INTEGER J ); BBLOCK; ARRAY INTEGER F( J ); LOGICAL TESTI; … END END M2;
(h)当最外层模块执行完,运行栈恢复到进入模块时 的情况,运行栈空。
main的数据 (空) 进入main函数的情况 OS
abp(0) → (空) 1
BBLOCK; 2 REAL X, Y; STRING NAME; M1: PBLOCK( INTEGER IND ) ; INTEGER X;
CALL M2( IND + 1 );
3 END M1; M24: PBLOCK( INTEGER J ); BBLOCK; ARRAY INTEGER F( J ); LOGICAL TESTI; … END END M2;
CALL M1( X / Y )
END
1 X, Y, NAME
2 M1: (IND); X;
《编译原理》教学课件-第7章 中间代码生成 7-2说明语句
D→id:T {enter(top(tblptr),,T.type,top(offset));
top(offset):=top(offset)+T.width }
N → { t:=mktable(top(tblptr));
push(t,tblptr);push(0,offset) }
图7.6 处理嵌套过程中的说明语句
3
每一个过程都建立一张独立的符号表 在符号表中有一个指针指向其外围过程的符号
表 在一个过程中,对于它内部定义的过程,都有
一个指针指向其内部过程的符号表
4
主程序:sort
readarray exchange quicksort
partition
5
sort
nil
header
a
x
readarray
offset:=offset+T.width }
Tinteger
{T.type:=interger; T.width=4;}
Treal
{T.type:=real; T.width=8;}
Tarray [num] of T1 { T.type:=array(num.val, T1.type);
T.width:=num.val*T1.width }
enterproc(table,name,newtable)在指针table指示的符 号表中为名字为name的过程建立一个新项
8
7.2.3 记录中的域名
Trecord LD end { T.type:=record(top(tblptr));
T.width:=top(offset);
pop(tblptr); pop(offset); }
编译原理课件chap7
第七章 语义分析和中间代码产生
要生成数组的引用代码,其主要问题是把常数C和 变数V 的计算与数组引用的文法联系起来。如果在图 7。10的文法中 id 出现的地方也允许下面产生式中的L 出现,则可把数组元素引用加入到赋值语句中。 L→id [ Elist ]| id Elist→Elist, E | E 为语句处理上的方便改写为: L→Elist ] | id Elist →Elist, E | id [ E 即把数组的名字id与最左下标表达式E 相联系,而不 是在形成L时与Elist相联系。其目的是使我们在整个下 标表达式串Elist的翻译过程中随时都知道符号表中相 应于数组名字id的全部信息。对于非终结符号Elist引 进综合属性arry,用来纪录指向符号表中相应数组名 字表项的指针。
第七章 语义分析和中间代码产生
7。1。2图表示法 我们要介绍的图表示法包括DAG与抽象语法树。 无循环有向图(Directed Acyclic Graph , 简称 DAG ).与抽象语法树一样,对于表达式中的每个子表 达式,DAG图中都有一个结点。一个内部结点代表一 个操作符,它的孩子代表操作数。两者不同的是,在 DAG图中代表公共子表达式的结点具有多个父结点, 而在一棵抽象语法树中公共子表达式被表示为重复的 子树。 表7。2 和抽象语法树的两种表示法都应掌握。 7。1。3 三地址代码 三地址代码是由下面一般形式的语句构成的序列: X:=y op z 其中,x、y、z为名字、常数或编译时产生的临时变量; op代表运算符号如定点运算符、浮点运算符、逻辑运 算符等。每个语句的右边只能有一个运算符。
第七章 语义分析和中间代码产生
7。3。1 简单算术表达式及赋值语句 课本P179图7。10给出了把简单算术表达式及赋 值语句翻译为三地址代码的翻译模式。该翻译模式中 说明了如何查找符号表的入口。属性表示id所 代表的名字本身。过程lookup()检查是否在符 号表中存在此名字的入口。如果有,则返回一个指向 该表项的指针,否则,返回nil表示没找到。 在图7。10的语义动作中,调用过程emit将生成的 三地址语句发送到输出文件中,而不是如表7。3中那 样建造非终结符号的code属性。注意: 在考试中如果遇到赋值语句的翻译应以图7。10为 准。 在有嵌套过程时如何使用图7。10,请参考课本P178179
要生成数组的引用代码,其主要问题是把常数C和 变数V 的计算与数组引用的文法联系起来。如果在图 7。10的文法中 id 出现的地方也允许下面产生式中的L 出现,则可把数组元素引用加入到赋值语句中。 L→id [ Elist ]| id Elist→Elist, E | E 为语句处理上的方便改写为: L→Elist ] | id Elist →Elist, E | id [ E 即把数组的名字id与最左下标表达式E 相联系,而不 是在形成L时与Elist相联系。其目的是使我们在整个下 标表达式串Elist的翻译过程中随时都知道符号表中相 应于数组名字id的全部信息。对于非终结符号Elist引 进综合属性arry,用来纪录指向符号表中相应数组名 字表项的指针。
第七章 语义分析和中间代码产生
7。1。2图表示法 我们要介绍的图表示法包括DAG与抽象语法树。 无循环有向图(Directed Acyclic Graph , 简称 DAG ).与抽象语法树一样,对于表达式中的每个子表 达式,DAG图中都有一个结点。一个内部结点代表一 个操作符,它的孩子代表操作数。两者不同的是,在 DAG图中代表公共子表达式的结点具有多个父结点, 而在一棵抽象语法树中公共子表达式被表示为重复的 子树。 表7。2 和抽象语法树的两种表示法都应掌握。 7。1。3 三地址代码 三地址代码是由下面一般形式的语句构成的序列: X:=y op z 其中,x、y、z为名字、常数或编译时产生的临时变量; op代表运算符号如定点运算符、浮点运算符、逻辑运 算符等。每个语句的右边只能有一个运算符。
第七章 语义分析和中间代码产生
7。3。1 简单算术表达式及赋值语句 课本P179图7。10给出了把简单算术表达式及赋 值语句翻译为三地址代码的翻译模式。该翻译模式中 说明了如何查找符号表的入口。属性表示id所 代表的名字本身。过程lookup()检查是否在符 号表中存在此名字的入口。如果有,则返回一个指向 该表项的指针,否则,返回nil表示没找到。 在图7。10的语义动作中,调用过程emit将生成的 三地址语句发送到输出文件中,而不是如表7。3中那 样建造非终结符号的code属性。注意: 在考试中如果遇到赋值语句的翻译应以图7。10为 准。 在有嵌套过程时如何使用图7。10,请参考课本P178179
编译原理chapter7 语义分析及中间代码生成
编译原理
chapter7
语义分三元式 三元式顾名思义就是带有三个域的记录结构。 三元式顾名思义就是带有三个域的记录结构。 一般形式为: (i)( ,arg1,arg2) 一般形式为: )(op, )( , ) 其中,( )为三元式的编号, 其中,(i)为三元式的编号,也代表了该式的运算 ,( 结果, , 的含义与四元式类似, 结果,op,arg1,arg2的含义与四元式类似,区别在 , 的含义与四元式类似 于arg可以是某三元式的序号,表示用该三元式的结果 可以是某三元式的序号, 可以是某三元式的序号 作为运算对象。 作为运算对象。
编译原理
chapter7
语义分析和中间代码生成
对于文法G[E]: E →E+T | T 例:对于文法 T→ digit 产生式 ) E→ E(1)+T E→ T T→ digit 语 法 分 析 栈 T + E … # 语义子程序 ) {E.Val=E(1).Val+T.Val} {E.Val=T.Val} {T.Val=digit} T.Val 语 义 ‘+’ ) E(1).Val 分 析 … 栈 #
编译原理
chapter7
语义分析和中间代码生成
三地址代码可以看成是抽象语法树一种线性表示。 三地址代码可以看成是抽象语法树一种线性表示。 线性表示 例: a=b*-c+b*-c = a + T1=-c T2=b*T1 T3=-c
*
b
* - b
c
c
T4=b*T3 T5=T2+T4 a=T5
编译原理
语义分析和中间代码生成
图表示法—抽象语法树 图表示法 抽象语法树 在语法树中去掉一些对翻译不必要的信息后 在语法树中去掉一些对翻译不必要的信息后,获得 去掉一些对翻译不必要的信息 的更有效的源程序的中间表示, 的更有效的源程序的中间表示,这种经过变换后的语法 树称为抽象语法树。 树称为抽象语法树。 内部结点代表操作符,它的孩子代表对应的操作数。 内部结点代表操作符,它的孩子代表对应的操作数。 例:a+a*(b-c)+(b-c)*d ( ) ( ) + + a a b
编译原理精选版演示课件.ppt
预测分析表
3
表驱动的预测分析程序模型
khk
4
实现步骤:
(1) 判断文法是否为LL(1)文法。 如果文法中含有左递归,必须先消除 左递归
(2)构造预测分析表 : Select(A ) (3)列出预测分析过程
khk
5
第6章:自底向上分析方法
自底向上分析方法,也称移进归约分析法
实现思想(是推导的逆过程):
对输入符号串自左向右进行扫描,并将输入符逐个 移入一个后进先出栈中,边移入边分析,一旦栈顶 符号串形成某个句型的可归约串时,就用该产生式 的左部非终结符代替相应右部的文法符号串,称为 归约。重复这一过程,直到归约到栈中只剩下文法 的开始符号时,则分析成功。
关键问题
khk
6
移进—规约分析(Shift-reduce parsing)
+
A a
可得 b <. a
由A→( B 且B+ ( B… 可得 (<. (
+
B aa…
可得 (<. a
+
B Aa )
可得 (<. A
khk
18
A(B(Aa) …)
(3) 求> .关系:
A(B…B
+
Aa
由S→bAb,且A…) 可得 ) > . b
A+…B 可得 B > . b
khk
88
例1:文法
SaAcBe A b A Ab B d
输入串abbcde#分析
khk
9
归约分析过程(移进归约):
步骤 1 2 3 4 5 6 7 8 9 10 1kh1k
符号栈 # #a #ab #aA #aAb #aA #aAc #aAcd #aAcB #aAcBe #S
编译原理-第七章解析资料
中间代码 中间 中间代码 生成器 代码 优化器
静态语义检查和中间代码生成器的位置
2018/10/26
TJNU-COCIE-WJW
5
第七章 语义分析和中间代码产生
中间语言 7.2 说明语句 7.3 赋值语句的翻译 7.4 分情况语句 7.5 回填技术 7.6 类型检查
7.1
2018/10/26 TJNU-COCIE-WJW 6
TJNU-COCIE-WJW 28
2018/10/26
例子:语句a:=b*-c+b*-c 的三元式表示
op arg1 arg2
(0) (1) (2) (3) (4) (5)
uminus * uminus * + Assign
c b c b
(1)
(0) (2) (3) (4)
a
2018/10/26
TJNU-COCIE-WJW
例:x
+ y * z翻译成 t1 := y * z t2 := x + t1
TJNU-COCIE-WJW 16
2018/10/26
1.一般形式(续)
三地址代码是AST或DAG的线性化表示
DAG图对应的三地址代码可能比相应的AST
对应的三地址代码要优化,因为可以复用中 间结果
2018/10/26
29
注意: 有些三地址语句要多个三元式表示 例子: x[i] := y op arg1 arg2 (0) [ ]= x i (1) := (0) y
y := x[i] op arg1 (0) =[ ] x (1) := y arg2 i (0)
2018/10/26
TJNU-COCIE-WJW
程序设计语言编译原理第三版第7章PPT课件
0
id
b
1
Id
c
2 uminus 1
3
*
0
4
id
b
5
id
c
6 uminus 5
7
*
4
8
+
3
9
id
a
10 assign 9
id c
id c
2
6 7 8
12
§7.1 中间语言
三、三地址代码 1.三地址代码:下面一般形式的语句构成的序列:
x:=y op z T1:=y*z, T2:=x+T1
x,y,z: 名字,常数,编译时产生的临时变量 op: 运算符号(如定点运算符,浮点运算符,逻辑运算符等)
点,一个内部结点代表一个操作符,它的孩子 代表操作数; 不同点:在一个DAG中代表公共子表达式的结点具有多个 父结点,而在一棵抽象语法树中公共子表达式 被表示为重复的子树。
9
§7.1 中间语言
例子:如图所示,为a+a*(b-c)+(b-c)*d的DAG
+
+
*
*
d
a
-
b
c
10
§7.1 中间语言
2.抽象语法树
称为三地址代码的原因: 每条语句通常包含三个地址,两个用来表示操作数,一个用
来存放结果。 具体实现:用记录表示,其中包含运算符和操作数的域。
13
§7.1 中间语言
2.四元式:带有四个域的记录结构 (op,arg1,arg2,result) 内容均是指针, 指向有关名字的符号表入口
14
§7.1 中间语言
E1’ op
7
§7.1 中间语言
编译原理演示文稿7
例:设有类Pascal程序段 program example(input,output);
type student=record no:integer; name:array[1..10] of char; score:integer end;
weekday=(sun,mon,tue,wed,thu,fri,sat); var st:array[1..50] of student;
name :array[1..20] of char; next:link end; 的无类型结构树或无环 有向图相等即可。检查类型等价也分成静态检查和动态检查。 由编译程序能完成的类型检查叫做静态类型检查;由目标程 序运行时所作的类型检查就称为动态类型检查。一般地,如 果要在生成的目标代码中完成类型检查,则目标代中不但要 保存数据的值,而且还保存该数据的类型,则可完工成相应 的动态类型检查。因算法语言的类型检查多数是静态的类型 检查,在这里仅介绍了静态的类型检查。
7.1.4 控制结构 一种程序设计语言的控制结构是该语言在程序运行期间
用于改变控制流的语言特征集合。它包括有条件控制转移, 条件执行、循环控制、过程序调用、转移和出口。编译程序 在翻译时必须保证源程序不能违法控制结构的语义。如 Pascal中只能从循环体内转向循环体外、C语言中不能从一 个函数转向另一个函数、BASIC中不能在循环体内修改循环 变量的值,而C没有这种限制。 例:错误的控制结构 begin
定义7.1 数据类型是对该类型数据(变量或常量)的取值是 否合法以及对该类型据的运算是否合法的一种说明。
实现和完成数据类型的合法性检查,它包括以下任务:
(1) 检查运算符作用在运算对象上的合法性,这一合法性保 证了该运算能产生正确的运算结果。
编译原理chapter7
– 其中一个标号是条件为真时对应的标号,称为“真值标号”, 另一个是条件为假时对应的标号,称为“假值标号”; – 并且语句中的这两个标号有可能还未填入。 – 在已填入这两个标号的目标地址的情况下,该语句的行为是: 计算某些条件,然后转移到其中的一个目标地址。
举例:Tiger表达式 a>b | c<d
可以将它转换为:
MEN
BINOP(PLUS, e1, e2) 简写为 +(e1, e2)
• 对于在当前过程中声明的存放在栈帧中的简单变量v,
MEN + TEMP fp CONST k
BINOP
PLUS TEMP fp CONST k
MEM (BINOP (PLUS, TEMP fp, CONST k))
Translation to Intermediate Code
计算机科学与技术学院 刘 慧
• Translate:转换为某人的母语或另一种语言。 • 编译器的语义分析阶段必须将抽象语法转换成抽象 机器代码(abstract machine code)。它可以在类 型检查之后或在类型检查的同时做这项工作。 • 直接将抽象语法转换成真实 的机器代码,尽管可以实现 但不利于可移植性和模块化
• MEM(e):开始于存储地址e的wordSize个字节的内容。
–当MEM作为MOVE操作的左子式时,它表示对存储地址e 的 “存储”; –在其他位置用:以参数表l 调用函数f ,子表达式f 的计算先于参数的计算,参数的计算则从左到右。 • ESEQ(s,e):先计算语句s 以形成其副作用,然后计算e 作为此表达式的结果。
typedef struct patchList_ *patchList; struct patchList_ {Temp_label *head; patchList tail;}; static patchList PatchList (Temp_label *head, patchList tail);
举例:Tiger表达式 a>b | c<d
可以将它转换为:
MEN
BINOP(PLUS, e1, e2) 简写为 +(e1, e2)
• 对于在当前过程中声明的存放在栈帧中的简单变量v,
MEN + TEMP fp CONST k
BINOP
PLUS TEMP fp CONST k
MEM (BINOP (PLUS, TEMP fp, CONST k))
Translation to Intermediate Code
计算机科学与技术学院 刘 慧
• Translate:转换为某人的母语或另一种语言。 • 编译器的语义分析阶段必须将抽象语法转换成抽象 机器代码(abstract machine code)。它可以在类 型检查之后或在类型检查的同时做这项工作。 • 直接将抽象语法转换成真实 的机器代码,尽管可以实现 但不利于可移植性和模块化
• MEM(e):开始于存储地址e的wordSize个字节的内容。
–当MEM作为MOVE操作的左子式时,它表示对存储地址e 的 “存储”; –在其他位置用:以参数表l 调用函数f ,子表达式f 的计算先于参数的计算,参数的计算则从左到右。 • ESEQ(s,e):先计算语句s 以形成其副作用,然后计算e 作为此表达式的结果。
typedef struct patchList_ *patchList; struct patchList_ {Temp_label *head; patchList tail;}; static patchList PatchList (Temp_label *head, patchList tail);
相关主题
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
第七章 运行时刻环境
许畅
南京大学计算机系 2018年春季
运行时刻环境
• 运行时刻环境
– 为数据分配安排存储位置 – 确定访问变量时使用的机制 – 过程之间的连接、参数传递 – 和操作系统、输入输出设备相关的其它接口
• 主题
– 存储管理:栈分配、堆管理、垃圾回收 – 对变量、数据的访问
2
存储分配的典型方式
– 同时还可以知道A、B合并为长度为300的块 – 修改双重链表,把A替换为A、B接合后的空闲块
• 注意:双重链表中一个结点的前驱并不一定是它 邻近的块
33
处理手工存储管理
两大问题
内存泄露 (Memory leak):未能删除不可能再被引用的 数据
悬空指针引用 (Dangling pointer):引用已被删除的数 据
36
可达性
• ቤተ መጻሕፍቲ ባይዱ达性就是指一个存储块可以被程序访问到 • 根集:不需要指针解引用就可以直接访问的数据
– Java:静态成员、栈中变量
• 可达性
– 根集的成员都是可达的 – 对于任意一个对象,如果指向它的一个指针被保存在
可达对象的某字段或数组元素中,那么这个对象也是 可达的
• 性质
• 一旦一个对象变得不可达,它就不会再变成可达的
何激活的
16
非局部数据的访问 (有嵌套过程)
• PASCAL中,如果过程A的声明中包含了过程B的 声明,那么B可以使用在A中声明的变量
• 当B的代码运行时,如果它使用的是A中的变量, 必须通过访问链访问
void A() { int x, y; void B() { int b; x = b + y; } void C() { B(); } C(); B();
• 返回代码序列 (Return sequence) 恢复机器状态, 使调用者继续运行
• 调用代码序列会分割到调用者和被调用者中
– 根据源语言、目标机器、操作系统的限制,可以有不 同的分割方案
– 把代码尽可能放在被调用者中
10
调用/返回代码序列的要求
数据方面
能够把参数正确地传递给被调用者 能够把返回值传递给调用者
– 堆存储:数据对象比创建它的过程调用更长寿
• 手工进行回收 • 垃圾回收机制
4
栈式分配
内容
活动树 活动记录 调用代码序列 栈中的变长数据
5
活动树
• 过程调用 (过程活动) 在时间上总是嵌套的
– 后调用的先返回 – 因此用栈来分配过程活动所需内存空间
• 程序运行的所有过程活动可以用树表示
25
存储管理器
• 基本功能
– 分配:为内存请求分配一段连续、适当大小的堆空间
• 首先从空闲的堆空间分配 • 如果不行则从操作系统中获取内存、增加堆空间
– 回收:把被回收的空间返回空闲空间缓冲池,以满足 其它内存需求
• 评价存储管理器的特性
– 空间效率:使程序需要的堆空间最小,即减小碎片 – 程序效率:运用内存系统的层次,使程序运行更快 – 低开销:使分配/收回内存的操作尽可能高效
– 分配方法
– 小尺寸的请求,直接在相应容器中找 – 大尺寸的请求,在适当的容器中寻找适当的空闲块 – 可能需要分割内存块,可能需要从荒野块中分割
31
管理和接合空闲空间
当回收一个块时,可以把这个块和相邻的块接合 起来,构成更大的块
有些管理方法 (如前面的Lea) 不需要进行接合
支持相邻块接合的数据结构
其他问题
空指针访问/数组越界访问
34
垃圾回收
垃圾
广义:不需要再被引用的数据 狭义:不能被引用 (不可达) 的数据
垃圾回收:自动回收不可达数据的机制,解除了 程序员的负担
使用的语言
Java、Perl、ML、Modula-3、Prolog、Smalltalk
35
垃圾回收器的设计目标
• 基本要求
记录的定长字段
15
非局部数据的访问 (无嵌套过程)
没有嵌套过程时的数据访问
C语言中,每个函数能访问的变量
函数的局部变量:相对地址已知,且存放在当前活动记录内, top_sp指针加上相对地址即可访问
全局变量:在静态区,地址在编译时刻可知
很容易将C语言的函数作为参数进行传递
参数中只需包括函数代码的开始地址 在函数中访问非局部变量的模式很简单,不需要考虑过程是如
– np – nq在编译时刻已知;从当前活动记录出发,沿访 问链前进np – nq次找到活动记录
– x相对于这个活动记录的偏移量在编译时刻已知
19
访问链的维护
• 当过程q调用过程p时
– p的深度大于q:根据作用域规则,p必然在q中直接定 义;那么p的访问链指向当前活动记录 (即q)
• 例如:sort调用quicksort(1, 9)
30
使用容器的堆管理方法
• 设定不同大小的块规格,相同的块放入同一容器 • 较小的 (较常用的) 尺寸设置较多的容器 • 如GNU的C编译器将所有存储块对齐到8字节边界
– 空闲块的大小:
– 16, 24, 32, 40, … , 512 – 大于512的按对数划分:每个容器的尺寸是前一容器的两倍 – 荒野块:可以扩展的内存块
– 语言必须是类型安全的:保证回收器能够知道数据元 素是否为一个指向某内存块的指针
– 类型不安全的语言:C/C++
• 性能目标
– 总体运行时间:不显著增加应用程序的总运行时间 – 空间使用:最大限度地利用可用内存 – 停顿时间:当垃圾回收机制启动时,可能引起应用程
序的停顿,这个停顿应该比较短 – 程序局部性:改善空间局部性和时间局部性
• 栈顶指针 (top_sp) 通常指向 固定长度字段的末端
12
调用代码序列的例子
• Calling sequence
– 调用者计算实在参数的值 – 将返回地址和原top_sp存放到被调用者的活动记录中;
调用者增加top_sp的值 (越过了调用者的局部数据、临 时变量、被调用者的参数、机器状态字段) – 被调用者保存寄存器值和其他状态字段 – 被调用者初始化局部数据、开始运行
目标程序的代码放置在代码区 静态区、堆区、栈区分别放置不同类型生命期的
数据值
3
静态和动态存储分配
• 静态分配
– 编译器在编译时刻就可以做出存储分配决定,不需要 考虑程序运行时刻的情形
– 全局常量、全局变量
• 动态分配
– 栈式存储:和过程的调用/返回同步进行分配和回收, 值的生命期与过程生命期相同
7
活动记录
过程调用和返回由控制栈 进行管理
每个活跃的活动对应于栈 中的一个活动记录
活动记录按照活动的开始 时间,从栈底到栈顶排列
8
运行时刻栈的例子
说明
a[11]为全局变量 main无局部变量 r有局部变量i q有局部变量i,
和参数m, n
r
r
9
调用代码序列
• 调用代码序列 (Calling sequence) 为活动记录分配 空间,填写记录中的信息
– 每个结点对应于一个过程活动 – 根结点对应于main过程的活动 – 过程p的某次活动对应的结点的所有子结点
– 表示此次活动所调用的各个过程活动 – 从左向右,表示调用的先后顺序
6
活动树的例子
• 快速排序程序
• 过程调用 (返回) 序列和活动 树的前序 (后序) 遍历对应
• 假定当前活动对应结点N, 那么所有尚未结束的活动对 应于N及其祖先结点
边界标记:在每个存储块的两端,分别设置一个 free/used位,并在相邻的位置上存放字节总数
双重链接的空闲块列表:列表的指针存放在空闲块中、 用双向指针的方式记录了有哪些空闲块
32
例子
• 相邻的存储块A、B、C
– 当回收B时,通过对free/used位的查询,可以知道B左 边的A是空闲的,而C不空闲
}
当A调用C,C又调用B时:
A的活动记录 C的活动记录 B的活动记录
当A直接调用B时:
A的活动记录 B的活动记录
17
嵌套深度
• 嵌套深度可根 深度1:sort 据源程序静态 确定
– 不内嵌于任何 其它过程的过 程,深度为1
– 嵌套于深度为i 的过程的过程, 深度为i + 1
深度2:readArray, exchange, quicksort
控制方面
能够正确转到被调用过程的代码开始位置 能够正确转回调用者的调用位置 (的下一条指令)
调用代码序列和活动记录的布局相关
11
活动记录的布局原则
• 原则
• 调用者和被调用者之间传递 的值放在被调用者活动记录 的开始位置
• 固定长度的项 (控制链、访问 链、机器状态字段) 放在中间 位置
• 早期不知道大小的项在活动 记录尾部
– 递归调用p = q:新活动记录的访问链等于当前记录的 访问链 (即和前一个q指向同一目标)
• 例如:quicksort(1, 9)调用quicksort(1, 3)
– p的深度小于等于q的深度:必然有过程r,p直接在r中 定义,而q嵌套在r中;p的访问链指向栈中r的活动记 录
• 例如:partition调用exchange
26
计算机的存储层次结构
27
程序中的局部性
程序具有高度的局部性 (Locality)
时间局部性:一个程序访问的存储位置很可能将在一 个很短的时间段内被再次访问
空间局部性:被访问过的存储位置的临近位置很可能 在一个很短的时间段内被访问
90%的时间用来执行10%的代码 局部性这一特性恰好可以充分利用计算机的层次
深度3:partition
18
许畅
南京大学计算机系 2018年春季
运行时刻环境
• 运行时刻环境
– 为数据分配安排存储位置 – 确定访问变量时使用的机制 – 过程之间的连接、参数传递 – 和操作系统、输入输出设备相关的其它接口
• 主题
– 存储管理:栈分配、堆管理、垃圾回收 – 对变量、数据的访问
2
存储分配的典型方式
– 同时还可以知道A、B合并为长度为300的块 – 修改双重链表,把A替换为A、B接合后的空闲块
• 注意:双重链表中一个结点的前驱并不一定是它 邻近的块
33
处理手工存储管理
两大问题
内存泄露 (Memory leak):未能删除不可能再被引用的 数据
悬空指针引用 (Dangling pointer):引用已被删除的数 据
36
可达性
• ቤተ መጻሕፍቲ ባይዱ达性就是指一个存储块可以被程序访问到 • 根集:不需要指针解引用就可以直接访问的数据
– Java:静态成员、栈中变量
• 可达性
– 根集的成员都是可达的 – 对于任意一个对象,如果指向它的一个指针被保存在
可达对象的某字段或数组元素中,那么这个对象也是 可达的
• 性质
• 一旦一个对象变得不可达,它就不会再变成可达的
何激活的
16
非局部数据的访问 (有嵌套过程)
• PASCAL中,如果过程A的声明中包含了过程B的 声明,那么B可以使用在A中声明的变量
• 当B的代码运行时,如果它使用的是A中的变量, 必须通过访问链访问
void A() { int x, y; void B() { int b; x = b + y; } void C() { B(); } C(); B();
• 返回代码序列 (Return sequence) 恢复机器状态, 使调用者继续运行
• 调用代码序列会分割到调用者和被调用者中
– 根据源语言、目标机器、操作系统的限制,可以有不 同的分割方案
– 把代码尽可能放在被调用者中
10
调用/返回代码序列的要求
数据方面
能够把参数正确地传递给被调用者 能够把返回值传递给调用者
– 堆存储:数据对象比创建它的过程调用更长寿
• 手工进行回收 • 垃圾回收机制
4
栈式分配
内容
活动树 活动记录 调用代码序列 栈中的变长数据
5
活动树
• 过程调用 (过程活动) 在时间上总是嵌套的
– 后调用的先返回 – 因此用栈来分配过程活动所需内存空间
• 程序运行的所有过程活动可以用树表示
25
存储管理器
• 基本功能
– 分配:为内存请求分配一段连续、适当大小的堆空间
• 首先从空闲的堆空间分配 • 如果不行则从操作系统中获取内存、增加堆空间
– 回收:把被回收的空间返回空闲空间缓冲池,以满足 其它内存需求
• 评价存储管理器的特性
– 空间效率:使程序需要的堆空间最小,即减小碎片 – 程序效率:运用内存系统的层次,使程序运行更快 – 低开销:使分配/收回内存的操作尽可能高效
– 分配方法
– 小尺寸的请求,直接在相应容器中找 – 大尺寸的请求,在适当的容器中寻找适当的空闲块 – 可能需要分割内存块,可能需要从荒野块中分割
31
管理和接合空闲空间
当回收一个块时,可以把这个块和相邻的块接合 起来,构成更大的块
有些管理方法 (如前面的Lea) 不需要进行接合
支持相邻块接合的数据结构
其他问题
空指针访问/数组越界访问
34
垃圾回收
垃圾
广义:不需要再被引用的数据 狭义:不能被引用 (不可达) 的数据
垃圾回收:自动回收不可达数据的机制,解除了 程序员的负担
使用的语言
Java、Perl、ML、Modula-3、Prolog、Smalltalk
35
垃圾回收器的设计目标
• 基本要求
记录的定长字段
15
非局部数据的访问 (无嵌套过程)
没有嵌套过程时的数据访问
C语言中,每个函数能访问的变量
函数的局部变量:相对地址已知,且存放在当前活动记录内, top_sp指针加上相对地址即可访问
全局变量:在静态区,地址在编译时刻可知
很容易将C语言的函数作为参数进行传递
参数中只需包括函数代码的开始地址 在函数中访问非局部变量的模式很简单,不需要考虑过程是如
– np – nq在编译时刻已知;从当前活动记录出发,沿访 问链前进np – nq次找到活动记录
– x相对于这个活动记录的偏移量在编译时刻已知
19
访问链的维护
• 当过程q调用过程p时
– p的深度大于q:根据作用域规则,p必然在q中直接定 义;那么p的访问链指向当前活动记录 (即q)
• 例如:sort调用quicksort(1, 9)
30
使用容器的堆管理方法
• 设定不同大小的块规格,相同的块放入同一容器 • 较小的 (较常用的) 尺寸设置较多的容器 • 如GNU的C编译器将所有存储块对齐到8字节边界
– 空闲块的大小:
– 16, 24, 32, 40, … , 512 – 大于512的按对数划分:每个容器的尺寸是前一容器的两倍 – 荒野块:可以扩展的内存块
– 语言必须是类型安全的:保证回收器能够知道数据元 素是否为一个指向某内存块的指针
– 类型不安全的语言:C/C++
• 性能目标
– 总体运行时间:不显著增加应用程序的总运行时间 – 空间使用:最大限度地利用可用内存 – 停顿时间:当垃圾回收机制启动时,可能引起应用程
序的停顿,这个停顿应该比较短 – 程序局部性:改善空间局部性和时间局部性
• 栈顶指针 (top_sp) 通常指向 固定长度字段的末端
12
调用代码序列的例子
• Calling sequence
– 调用者计算实在参数的值 – 将返回地址和原top_sp存放到被调用者的活动记录中;
调用者增加top_sp的值 (越过了调用者的局部数据、临 时变量、被调用者的参数、机器状态字段) – 被调用者保存寄存器值和其他状态字段 – 被调用者初始化局部数据、开始运行
目标程序的代码放置在代码区 静态区、堆区、栈区分别放置不同类型生命期的
数据值
3
静态和动态存储分配
• 静态分配
– 编译器在编译时刻就可以做出存储分配决定,不需要 考虑程序运行时刻的情形
– 全局常量、全局变量
• 动态分配
– 栈式存储:和过程的调用/返回同步进行分配和回收, 值的生命期与过程生命期相同
7
活动记录
过程调用和返回由控制栈 进行管理
每个活跃的活动对应于栈 中的一个活动记录
活动记录按照活动的开始 时间,从栈底到栈顶排列
8
运行时刻栈的例子
说明
a[11]为全局变量 main无局部变量 r有局部变量i q有局部变量i,
和参数m, n
r
r
9
调用代码序列
• 调用代码序列 (Calling sequence) 为活动记录分配 空间,填写记录中的信息
– 每个结点对应于一个过程活动 – 根结点对应于main过程的活动 – 过程p的某次活动对应的结点的所有子结点
– 表示此次活动所调用的各个过程活动 – 从左向右,表示调用的先后顺序
6
活动树的例子
• 快速排序程序
• 过程调用 (返回) 序列和活动 树的前序 (后序) 遍历对应
• 假定当前活动对应结点N, 那么所有尚未结束的活动对 应于N及其祖先结点
边界标记:在每个存储块的两端,分别设置一个 free/used位,并在相邻的位置上存放字节总数
双重链接的空闲块列表:列表的指针存放在空闲块中、 用双向指针的方式记录了有哪些空闲块
32
例子
• 相邻的存储块A、B、C
– 当回收B时,通过对free/used位的查询,可以知道B左 边的A是空闲的,而C不空闲
}
当A调用C,C又调用B时:
A的活动记录 C的活动记录 B的活动记录
当A直接调用B时:
A的活动记录 B的活动记录
17
嵌套深度
• 嵌套深度可根 深度1:sort 据源程序静态 确定
– 不内嵌于任何 其它过程的过 程,深度为1
– 嵌套于深度为i 的过程的过程, 深度为i + 1
深度2:readArray, exchange, quicksort
控制方面
能够正确转到被调用过程的代码开始位置 能够正确转回调用者的调用位置 (的下一条指令)
调用代码序列和活动记录的布局相关
11
活动记录的布局原则
• 原则
• 调用者和被调用者之间传递 的值放在被调用者活动记录 的开始位置
• 固定长度的项 (控制链、访问 链、机器状态字段) 放在中间 位置
• 早期不知道大小的项在活动 记录尾部
– 递归调用p = q:新活动记录的访问链等于当前记录的 访问链 (即和前一个q指向同一目标)
• 例如:quicksort(1, 9)调用quicksort(1, 3)
– p的深度小于等于q的深度:必然有过程r,p直接在r中 定义,而q嵌套在r中;p的访问链指向栈中r的活动记 录
• 例如:partition调用exchange
26
计算机的存储层次结构
27
程序中的局部性
程序具有高度的局部性 (Locality)
时间局部性:一个程序访问的存储位置很可能将在一 个很短的时间段内被再次访问
空间局部性:被访问过的存储位置的临近位置很可能 在一个很短的时间段内被访问
90%的时间用来执行10%的代码 局部性这一特性恰好可以充分利用计算机的层次
深度3:partition
18