260 likes | 422 Views
编译原理与技术. 西安电子科技大学 软件工程研究所 刘坚. 教学内容与要求. <1> 内容 本课程的内容是建立在本科 < 编译原理 > 基础上的,尽量避免重复本科已有的内容。 为了整个课程的一致性,而且由于学习方法的螺旋式特性,一些已学过的内容也会有所涉及,但会在原有基础上提高一步。重点放在本科课程中没有涉及的领域。 主要介绍如下内容,并在兼顾理论与实现两个方面上进行讨论。. 编译程序编写工具 --lex 和 yacc :学会使用 lex 和 yacc 进行程序设计;
E N D
编译原理与技术 西安电子科技大学 软件工程研究所 刘坚
教学内容与要求 <1> 内容 本课程的内容是建立在本科<编译原理>基础上的,尽量避免重复本科已有的内容。 为了整个课程的一致性,而且由于学习方法的螺旋式特性,一些已学过的内容也会有所涉及,但会在原有基础上提高一步。重点放在本科课程中没有涉及的领域。 主要介绍如下内容,并在兼顾理论与实现两个方面上进行讨论。 • 编译程序编写工具--lex和yacc:学会使用lex和yacc进行程序设计; • 词/语法分析器核心算法:有限自动机的有效构造算法和LALR(1)分析器的构造算法; • 语法制导翻译:属性、L属性的自下而上计算;
类型检查:类型理论的发展、类型与类型检查、多态处理、封装与继承的实现技术等,类型系统的形式化方法简介;类型检查:类型理论的发展、类型与类型检查、多态处理、封装与继承的实现技术等,类型系统的形式化方法简介; • 动态语义:指称语义入门,原理及其应用; • 代码优化:局部与循环优化,全局数据流分析技术。 教学内容与要求(续1) <2> 要求 • 做适当作业,期末统一收缴一次,并进行一次作业讲解(在课程总复习中进行)。作业要求独立做(不计分);也可以不做,但不要抄;若合作做,则几个人合交一份。 • 做上机作业,实现一个Pascal子集编译程序的全过程:两个学生一组,可以采用任何类似的Lex/Yacc工具。上机作业计分(15%左右),重点考核上机报告和完成的软件。 • 期终考试:闭卷考试。严格要求(按真实成绩给分) • 适当读参考文献。 • 选一个课代表。
参考文献 <1> 编译的相关理论与技术 • A.V.Aho,J.D.Ullman,“The Theory of Parsing,Translation,and Compiling,Volume I:Parsing,”Prentice-Hall Inc. 1972 • A.V.Aho,J.D.Ullman,“The Theory of Parsing, Translation,and Compiling,Volume Ⅱ:Compiling,”Prentice-Hall Inc. 1973 • 人民邮电出版社, Aho等, “编译原理 技术与工具”(影印版)(主要参考书,可作教材,上机作业题目) • 高等教育出版社,Andrew W.Appel,“现代编译程序实现-Java语言”(影印版) • 机械工业出版社,Steven S.Muchnick,“高级编译器设计与实现” (影印版)
<1> 程序设计语言原理与设计 • R.W.Sebesta"Concepts of Programming Languages",机械工业出版社(影印版) • Terrence W.Pratt, Marvin V.Zelkowitz "Programming Languages - Design and Implementation", third edition, Prentice-Hall International, Inc. 1996 • David A. Watt “Programming Language Syntax and Semantics,”Prentice Hall Inc. 1991 参考文献(续) • <3> 编译器构造 • Axel T.Schreiner, H.George Friedman, Jr. "Introduction to Compiler Construction with UNIX", Prentice Hall,Englewood Cliffs,NJ07632,1985 • 杨作梅译,(Jobn R.Levine, Tony Mason & Doug Brown著), "lex与yacc",机械工业出版社,2003 • "how to use lex&yacc"(互联网)
第一章 概述1.1 要求与目的 <1> 紧密相关的三个领域 程序设计语言的应用-程序设计(PLA) 程序设计语言的翻译-编译器的构造(PLT) 程序设计语言的设计-语法、语义(PLD) • <2> CCC 2002中的基本要求 • 程序设计基础(PF):程序设计基本结构、算法与问题求解、基本数据结构、递归、事件驱动程序设计。(PLA) • 程序设计语言(PL):程序设计语言概论、虚拟机、语言翻译简介、声明和类型、抽象机制、面向对象程序设计(以上是核心);函数程序设计、语言翻译系统、类型系统、程序设计语言的语义、程序设计语言的设计(以上是选修)。(PLA、PLT、PLD)
<3> 目的 1.2 程序设计语言简述 • 深刻理解程序设计语言的应用、翻译、设计等方面的基本原理与方法; • 学会用编译器编写工具(LEX/YACC)进行语言处理软件的设计; • 初步认识动态语义的形式化描述方法。 1.2 程序设计语言简述 编译器处理的对象是程序设计语言,因此在对程序设计语言了解的基础上和它们与编译器的关系上,有必要在以下几个方面再进行一些简单讨论。
从年代看 • 50s~:Fortran、LISP、COBOL、Algol 60/68... • 70s~:C、Pascal、Prolog、Smalltalk、Modula 2... • 80s~:C++、Ada83/Ada95、Java、Modula 3... • 从抽象级别看 • 过程→抽象数据类型→类(封装→继承) • 从工作方式看 • 非结构化→结构化 • 顺序→并行→基于消息传递 • 单平台→跨平台 • 从程序设计范型看 • 过程式语言 • 面向对象语言 • 函数式语言 • 非算法式语言(形式化) • 脚本式语言 1.2.1 程序设计语言的发展
1.2.2 影响程序设计语言发展的因素 • 应用需求的推动:软件越做越大,功能越来越强,要求语言的抽象程度越来越高 • 语言实现的限制(编译器是否好编写):典型的例子如Algol68和Ada83 • 语言与编译器之间的相互推动与制约:自由格式,保留字,多继承等。 1.2.3 程序设计语言“好”的标准(属性) <1> 清晰、简单、与一致性:可读性、可编译性、可维护性 <2> 对应用需求的自然实现:设计思想→自然过渡到语言描述 <3> 支持抽象:x, y : integer → x+y a, b : array of ...→ a+b?
<4> 便于程序验证 1.2.3 程序设计语言“好”的标准(续) • 形式化验证:在源程序中通过加入前置条件、后置条件,根据运算不变量进行数学推理,从而检验程序的逻辑正确性;或者直接采用动态语义的形式化描述进行程序验证(建立数学模型,通过数学计算得到结果,看是否符合预期结果)。 • 静态阅读:风格好的程序通过阅读可以检查出部分逻辑错误,但不是全部的错误。 • 实例测试:此方法是最常采用的、也是最成熟的方法。所需考虑的首要问题是如何生成实例才能达到程序的完全覆盖,以及如何进行测试。 <5> 可移植性:任何一段源程序,在A机上编译运行,与在B机上编译运行,得到同样的结果。 <6> 使用代价小:程序运行代价、程序翻译代价(编译)、程序维护代价(整个程序的生命周期)。
1.2.4 环境对语言的影响(对编译器的要求) <1> 语言环境包括开发环境(1,2)与运行环境(3-6)。 • 命令行(独立的):编辑、编译、运行等均是独立进行的; • 集成环境:编辑→编译→调试→运行(要求编译器提供编译和调试信息,如编译阶段和运行阶段不同错误分别对源程序的对应等)。作为其他集成环境的一部分,如together-J、rose等。 • 批处理环境(一般是CPU/存储器密集型):大型信息处理系统:日终处理、数据更新等。 • 交互式环境(I/O密集型):游戏、实时事务处理(如查询、交易等)。 • 嵌入式环境(实时响应关键型):控制系统(如生产线)军事武器(如飞机、火炮、舰船等)。广泛的民用领域,如手机、数码相机等。 • 分布式环境:如分布式系统、网络系统与Internet等。
<2> 运行环境的复杂性 1.2.4 环境对语言的影响(续) 多种环境的混合,如计算机集成制造系统:信息管理、生产控制、精密加工等;又如大型信息、金融系统:批处理、交互查询,以及网络与Internet等。
1.3 编译与解释 编译与解释是翻译语言的两种基本方法。解释器与编译器的主要区别在于程序运行时的控制权在解释器而不在目标程序。 • 1.3.1 各自的特点 • 编译器:生成目标代码,运行速度快、空间占用少,效率高;动态特性和可移植性差(典型应用:批处理环境和嵌入式环境)。 • 解释器:动态特性、可移植性好,但是效率低(典型应用:交互式环境、分布式环境)。 • 1.3.2 解释的动态交互特性可以胜任编译器无法胜任的工作 • Java虚拟机:编译器生成字节代码,虚拟机解释字节代码。(其它语言的编译器若能生成Java的字节代码,结果如何?) • 嵌入式SQL中查询语句的动态生成:解决了系统运行时才能确定数据的查询和处理。 • 编译与解释共存。
例1.1有一个数据库f_table,存放各帐户的如下信息:例1.1有一个数据库f_table,存放各帐户的如下信息: account_no:0000001-9999999 name: xxxxxxxxxx term: 3,6,9,12,24,36,60 amount: 100.00-9999999.99 open_date: 19860101-当前值 state_flag:normal,closed,lost,... 现有语句: EXEC SQL SELECT name, amount FROM f_table WHERE conditions 数据库查询问题 考察f_table,它有6个字段,每个字段分别取若干值,因此,从此表中查询时的查询条件(conditions)应该是6个字段中各值的集合元素个数的乘积。 问题:当某个查询涉及m个表,每个表中平均有k个字段,每个字段平均有n个取值,最坏查询条件有多少个?如何写查询语句?
对于任何一个应用系统,不可能枚举所有情况,并分别写出这些查询语句。唯一可行的方法是:运行时根据用户输入的查询字段和限制条件,拼接成一条类似下述的语句:对于任何一个应用系统,不可能枚举所有情况,并分别写出这些查询语句。唯一可行的方法是:运行时根据用户输入的查询字段和限制条件,拼接成一条类似下述的语句: EXEC SQL SELECT f1, f2, ... FROM t1,t2,... WHERE conditions; 并把上述语句存放在一个字符串sql_stmt中。而系统提供下述机制: EXEC SQL PREARE s FROM :sql_stmt; EXEC SQL EXECUTE s ..; 动态查询语句 DBMS的处理方法是:在运行系统中嵌入一段解释程序,它首先对sql_stmt进行分析(检查是否语法语义正确),形成串s的中间表示,然后执行s,从而实现动态查询。若 sql_stmt=“EXEC SQL SELECT name, amount FROM f_table WHERE term>12” 则最后结果是查出所有存期大于一年的人的姓名和帐号。
1.4 程序设计语言的语法与语义 编译器的设计与实现,首先是对所要进行编译的语言进行描述,只有精确地描述语言,才能有效地实现语言。 完整的程序设计语言,包括两部分描述:语法(syntax)和语义(semantics)描述。 1.4.1 语法 程序设计语言的语法可以用上下文无关文法(Context-free Grammar,CFG)描述,它是一种很好的描述方法,而且已有很成熟的技术构造十分有效的上下文无关文法的分析器,因此可以利用工具自动生成这类语法分析器。 由于程序设计语言的语法是决定程序基本特性(如可读性、可维护性、可编译性等)的基础,因此程序设计语言语法设计的好坏就成为程序设计语言好坏的重要因素之一。 随着程序设计语言的发展,语法的设计也形成了一些公认的准则,下述是一些典型的例子。
<1> 自由书写格式:FORTRAN、LEX/YACC都不是自由书写格式。 • <2> 设立保留字: • PL/1中:IF THEN THEN THEN=ELSE; ELSE ELSE=THEN • <3> 符合科学的、或历史形成的习惯性描述: • C/C++中:a=b和a==b • 其他语言:a:=b和a=b • <4> 避免超前扫描: • FORTRAN:DO 5 I = 1.25 (1) • DO 5 I = 1, 25 (2) • 对于DO5I = 1.25;要超前扫描到'.'时才确定是赋值句。 • 对于DO 5 I = 1,25;要超前扫描到','时才确定是循环语句。 • <5> 减少可能的书写错误: • C的注释:/* this is a comment */ • C++的注释:// this is a comment • <6> 大小写不敏感:Pascal、Ada等大小写不敏感,而C是敏感的。 1.4.1 语法(续)
语义用于确定语言的意义,一般被分为两类:静态语义(static semantics)和动态语义(run-time semantics)。 1.4.2 语义 <1> 静态语义 静态语义主要是为了解决上下文无关文法不能应付的问题,它实际上是一组上下文限制(Contextual Restrictions,有些文献中就是把静态语义称为上下文限制),用来确定语法上正确的程序是否实际上也是有意义的。静态语义主要用于检查: • 被引用的标识符是否被说明; • 运算符和操作数是否类型兼容,操作数之间是否类型兼容等; • 过程调用时参数的个数及类型是否与过程定义时的相容,等等。
<2> 动态语义 动态语义用来规定程序做什么,也就是如何动作。 1.4.2 语义(续) 原理上讲,无论是语法还是语义,均应进行形式化的描述。但是由于种种原因,语言的文本往往是对文法进行形式化描述,而对语义进行非形式化的描述,带来的问题是不能精确描述语义。
对于一个语言的定义,一定要严格、准确,否则就不可能写出精确反映该语言实际意义的编译程序。对于一个语言的定义,一定要严格、准确,否则就不可能写出精确反映该语言实际意义的编译程序。 1.4.3 为什么要精确定义语义 例1.2对于语句:L: goto L; 一般语言定义中只是简单说goto语句的作用是把控制转向其所指的标号所在位置。如果这样看,上述语句就是合法的。但是很明显,一旦程序执行起来就会陷入死循环,永不停机。 在这种情况下,上述语句应该是非法的。在实际处理上,有的编译程序禁止这样的goto语句,而有的编译程序却不与理睬。 例1.3对于Pascal语句:(i<>0) and (k div i >10) 该语句的语法完全正确,但是当语句被执行时,情况就不同了。考虑i=0的情况下,若编译时对条件语句采用的是短路计算,语句就可以正确执行; 若不采用短路计算,则会出现致命错误(0作为除数)。 但在原始的Pascal定义中,对是否采用短路计算并没有明确规定,因此有的编译程序采用短路计算,而有的不采用。
1.4.3 为什么要精确定义语义(续) 从某种意义上讲,编译器本身实际上起着定义语言的作用。即对于某种语言,编译器选择如何接受和翻译该语言。这就使得一段程序在一个机器上可以正确运行,在另一个机器上就不可以,而这恰恰违背了通用程序设计语言的初衷。
语义的形式化描述至少在下述三个方面起重要作用:语义的形式化描述至少在下述三个方面起重要作用: • 精确描述语义 • 程序的正确性验证 • 程序自动生成 1.4.4 语义的形式化描述 <1> 静态语义 静态语义形式化描述最常采用的是属性文法(attribute grammars),它实际上是为产生式中的符号扩充属性。因此,也可以认为属性文法是对上下文无关文法的扩充,二者结合起来,完整地定义出合法的程序。 由于属性文法对静态语义的描述并不是独立的,需要与文法捆绑在一起,因此被认为是半形式化的描述。 静态语义的形式化描述,特别是类型系统的形式化描述,是当前的一个研究热点。 下边来看两个属性文法的例子。
<1> 静态语义(续) 例1.4对于产生式E→E1+E2,我们可以为E添加属性E.type(类型)、E.val(值)等,然后,可以对属性进行下述计算: if E1.type=integer and E2.type=integer then E.type:=integer; gen(+,E1.val, E2.val, E.val); else E.type=type_error; 例1.5 S→L1 : goto L2;引入属性.val,则: if L1.val = L2.val then S.type := type_error else gen(jmp, , , L2.val); 上述两个例子中,可以看出,不管是.type还是.val,一般均是静态可确定的(对于静态可编译语言来说),因此可以采用属性文法的方式来描述这些语义。 但是对于表达式的动态特性,此描述方法就失效了。例如x+y的结果超出了计算机的表示范围,或者是x/y在运行时y的值为0等。
动态语义的形式化描述没有静态语义成熟。多年来人们也一直在动态语义形式化描述的领域中进行研究。比较典型的形式化描述方法包括:动态语义的形式化描述没有静态语义成熟。多年来人们也一直在动态语义形式化描述的领域中进行研究。比较典型的形式化描述方法包括: • 操作语义(operational or interpreter modes) • 公理语义(Axiomatic definitions) • 指称语义(Denotational modes) • 其中指称语义得到较多的关注。从某种意义上讲,指称语义也可以被认为是对上下文无关文法的扩充,或者是一种语法制导翻译,但是它的结构的意义(meaning)是由其直接成员的意义来定义的。例如: <2> 动态语义 Evaluate[T1+T2] = Evaluate[T1] is integer and Evaluate[T2] is integer → range(Evaluate[T1]+Evaluate[T2]) else error 对T1+T2求值,则首先对T1和T2分别进行求值,若二者都是整型数,则T1+T2的意义就是它们两个值相加,且在合理的值域中,否则表达式取值error。