众所周知,结构化程序设计并不提倡使用goto或类似语句。不过,即便如此,包括Pascal、C在内的大多数高级语言都提供了goto语句。只要正确设计、合理应用,对于某些编程高手(尤其是汇编高手)而言,goto语句还是有着其他控制结构无法比拟的灵活性与优越性。
下面,笔者先来谈谈goto语句翻译的几个重点。
(1)向前跳转与向后跳转。通常,goto语句的跳转分为两种情况:第一种,目标标号的位置定义点在goto语句之前,也称为“向前跳转”。第二种,目标标号的位置定义点在goto语句之后,也称为“向后跳转”。实际上,对于Pascal而言,两者的分析与处理并不存在差异。然而,C语言编译器在分析goto语句时,就必须严格区别这两种情形。C语言没有标号声明机制,在分析向后跳转的goto语句时,由于目标标号的定义点位于goto语句之后,使编译器无法获得目标标号的任何信息。在这种情况下,编译器又该如何处理呢?无论何种处理方案都必须保证任何goto语句的目标标号都是存在唯一定义点的。基于这个前提,通常的处理方案是将目标标号登记到符号表中,并设置特定的标志用于标识此标号存在引用点但不存在定义点。编译器一旦获得该标号的定义点信息,及时更新该标识的符号表信息即可。当编译器分析完输入源程序后,必须逐一检查符号表中的标号信息,判断是否存在仅有引用点却没有定义点的标号信息,如果存在,则给出相应的出错提示。当然,有些编译原理书籍也介绍了一些其他的处理方案,如回填链等。
(2)过程内跳转与过程间跳转。这是一个比较复杂的问题,当然,函数也存在同样的问题。现代高级语言对此有比较严格的限制,即goto语句仅能用于实现过程内跳转,而不允许进行过程间跳转。熟悉汇编语言的读者可能会有疑惑,有些目标机的JMP指令是可以实现全空间跳转的,为什么goto语句只能进行过程内跳转呢?这是因为汇编语言程序设计中经常使用静态存储分配机制。而编译器生成的目标代码更多使用的是栈式存储分配机制。过程间跳转涉及一些复杂的存储管理方面的问题,在一般情况下,编译器是很难实现真正意义上的过程间跳转的。当然,这也并非完全不可能的。在早期,有些静态分配存储空间的编译器就可以比较方便地实现过程间跳转,而不需要付出太大的代价。
下面,再来看看goto语句的相关语义子程序。
程序5-23 semantic.cpp
第5行:在标号信息表中,检索目标标号是否已声明。注意,检索范围仅限于本过程(函数)内,即使是主程序中声明的标号也不允许在子过程中被引用。Pascal语言明确规定标号定义或引用前必须在声明部分中给出相应的说明,而C语言并没有对应的要求。
第10、11行:生成JMP语句,并设置目标标号。(www.daowen.com)
第12行:设置目标标号的m_bUse属性,表示该标号已经存在引用点。
最后简单说明一下Neo Pascal如何保证变量声明、定义的有效性。实际上,有以下几种情况需要考虑,请参考表5-11。
表5-11 标号有效性列表
注意,前三种情况在分析goto语句或定义标号时都能够识别与处理。不过,由于goto语句的向后跳转的情形,使得最后一种检查不能即时进行。在分析完一个输入源程序后,编译器会对标号信息表作一次全局分析,确定哪些标号仅有引用没有定义。
程序5-24 semantic.cpp
这是整个语义分析阶段最后被调用的语义子程序,它的主要作用就是检查是否存在仅有引用没有定义的标号。注意,编译器分配的临时标号是不需要作检查的,只需跳过即可。
免责声明:以上内容源自网络,版权归原作者所有,如有侵犯您的原创版权请告知,我们将尽快删除相关内容。