表达式是程序设计语言的一个重要组成部分,包括汇编语言在内的绝大多数语言都引入了表达式的概念。通常,表达式主要由两个部分组成,即操作数与运算符。有些读者可能会认为一门语言的运算符个数也是非常有限的,因此,仅从语义角度而言,表达式并不难理解。不过,不能忽略一个重要的元素——类型系统。当类型系统被引入表达式之后,很多看似简单的问题可能会变得异常复杂,尤其是试图借助于一个软件系统实现相关功能时。例如,Pascal表达式i+4中的加法运算符的精确语义是什么呢?这个问题并不好回答,根据i的实际类型,加法运算符至少可能存在两种不同的语义,即无符号整数加法、有符号整数加法。对于编译器设计者而言,仅仅回答这个运算符为加号是远远不够的。通常,需要了解更深刻的语义。在上例中,如果考虑语言的详细数据类型,由于各种数据类型的存储空间不同,可能会得到更多关于加法运算符的语义理解。理论上讲,两种不同类型数据之间的加法运算都存在类型转换。不过,其中只有很小一部分转换是显式的,即需要程序员编码完成的,大多数转换都是隐式的,完全是编译器自动分析完成的。不过,在编程过程中,程序员一般很少理会隐式转换机制,甚至有时根本没有意识到隐式转换的存在,因为有太多太多的工作是由编译器代劳的。程序员可能很难想象如果书写每个表达式都是需要显式完成类型转换的情形。本章将从类型入手,讨论几个与表达式分析相关的话题,引导读者从编译器设计的视角重新认识表达式。下面,简单谈谈本章将涉及的几个核心问题:
(1)类型系统。这是表达式翻译的核心,没有设计相对精良的类型系统,就绝对无法进行表达式的翻译。笔者将从类型描述、类型等价、类型相容、类型转换等方面阐述编译器类型系统的设计,并详细介绍Neo Pascal的相关设计思想与实现。
(2)数组、结构、指针等复杂变量的寻址。实际上,这个问题的本质就是讨论操作数的寻址翻译。在翻译表达式之前,编译器必须得到所需的操作数寻址的相关IR,否则表达式的IR是无法得到的。简单变量的寻址是比较容易的,而数组、结构、指针等复杂变量的寻址就比较困难了。当然,更为复杂的就是这些元素的嵌套使用,如何正确无误地生成IR是编译器设计者必须关注的。(www.daowen.com)
(3)常量的计算。实际上,这是一个优化方面的技术(即常量折叠),但是并不意味着语义分析阶段就不必理会了。常量折叠的思想是很简单的,它试图将操作数皆为常量的表达式直接由编译器计算得到相应的结果,以备后用。例如,表达式i=5+3,由于加法运算的两个操作数都是常量,所以5+3的计算就可以由编译器完成,并不需要生成相应的加法IR,而是直接翻译为i=8相应的IR语句即可。当然,读者同样不要小觑常量折叠。由于类型系统的存在,要完成真正意义上的常量折叠是有些难度。
(4)表达式翻译。最后,就是如何正确理解与翻译表达式了。读者可能立刻会联想到繁复的运算符优先级,难道这是表达式翻译的核心吗?答案是否定的。虽然,每位C程序员曾几何时必定都被C语言的15级运算优先级折磨得苦不堪言,但是,运算符优先级却不是表达式翻译所关注的。甚至读者会发现表达式翻译时对其根本不需要关注,这是非常神奇的。当然,在使用递归下降分析法时,有时会利用运算优先级来简化程序设计,那是一种非常精巧的设计结构。有兴趣的读者可以参考lcc的实现。这里就不再深入讨论了。
免责声明:以上内容源自网络,版权归原作者所有,如有侵犯您的原创版权请告知,我们将尽快删除相关内容。