第六章-Javac符号表

 

需要参考:

(1)Architecture of a Java Compiler

(2)关于符号Symbol第一篇

(3)关于符号Symbol第二篇

(4)关于类型Type

(5)关于作用域范围Scope

(6)

 

Java语义分析阶段的一个主要工作是符号表的管理。符号表(symbol table)也称为环境(environment),其作用是将标识符映射到它们的类型和存储位置。在处理类型、变量和方法的定义时,顺便将这些标识符存储到符号表中,同时为这些标识符绑定相关的信息。每当发现标识符的使用(即非定义)时,便在符号表中查找这些标识符的信息。

 

在进行标识符相关信息的存储时,需要考虑到标识符的类型、作用域,而对于方法来说,至少还需要保存参数数量与类型,以及返回值等。

 

1、符号

写一段Java程序,不难看出在这段程序中,除了引用已经存在的标识符,剩下的就是定义新的标识符已经引用这些新定义的标识符了,如下: 

package com.test20;

class Test{
	int a = 0;
	public void tm(){
		String s = ""+a;
	}
}

定义了一个类Test,一个变量a与一个方法tm。在tm()方法中,有对已经声明过的类型的引用,如String,也有对刚声明的变量a进行引用。不难看出,在对已定义好的标识符进行引用时,需要知道它们的一些相关信息,比如如果是变量,则直接使用标识符即可,如果标识符表示的是方法,还需要以方法的形式来调用。那么在获取标识符时,还需要获取到这些标识符的相关信息,如引用一个已经声明的类时,这个类中定义了哪些方法,当前是否可以访问到。在Java编译器中,每声明一个标识符时,就通过Symbol及其子类来表示。

第六章-Javac符号表_第1张图片

 

下面来认识一下这些重要的类。

 

1.1 Symbol(介绍属性及方法)

 

1.2 TypeSymbol

 

 

1.3 VarSymbol

预定义符号的输入

对操作符的处理 

 

1.4 MethodSymbol

 

 

2、组织符号表

 

2.1 Scope属性的介绍

在上一章介绍了在代码编写的过程中可能会定义出各种不同的符号,这一章将详细介绍这些符号是如何符号表来组织的。在Javac中定义了一个Scope类,这个类代表了符号的作用,例如:

class Test{
	int x = 1;
	{
		float x = 2;
	}
	public void m(){
		long x = 2;
	}
}

很显示,上面3个变量的定义都不在同一个作用域范围内。所以相同作用域内的符号应该存放到同一个Scope对象中。一个Scope作用域内定义的多个符号用数组来存储,不过并不是直接存储Symbol,而是将Symbol进一步封装为Entry对象。这个数组在Scope中的定义如下:

 

/** A hash table for the scope's entries.
*/
Entry[] table;

 

Entry类的定义及重要的属性如下:

/** A class for scope entries.
 *   shadowed指针指向桶中的下一个表项,shadowed意为隐蔽之义,sibling指针指向
 *   下一个填入哈希表中的表项,和符号的范围scope
 */
public static class Entry {

    /** The referenced symbol.
     *  sym == null   iff   this == sentinel
     */
    public Symbol sym;

    /** An entry with the same hash code, or sentinel.
     */
    private Entry shadowed;

    /** Next entry in same scope.
     */
    public Entry sibling;

    /** The entry's scope.
     *  scope == null   iff   this == sentinel
     *  for an entry in an import scope, this is the scope
     *  where the entry came from (i.e. was imported from).
     */
    public Scope scope;
    // ...
}

sym属性就是这个Entry代表的符号,scope表示这个符号所属的作用域。而shadowed与sibling指针是用来解决冲突的。由于这些定义出来的符号会被被频繁引用,那么符号表的组织就需要一个高效的组织方式,而Java编译器采用了Hash方式,采用Hash来存储符号,必然会产生冲突,通过单链表与二次冲突检测法来解决冲突。一般标识符的名称不同时,通过二次冲突检测法来避免冲突,如果标识符名称相同,则通过单链表来避免冲突,如下: 

class Test{
	class obj{}
	int obj = 0;
	public void obj(){
		
	}
}

在同一个作用域内声明了三个不同类型但是名称相同的符号,这些符号最终都会占用hash表中相同的槽位。并且通过shadowed相连,新加入的插入链表的头部,这样查找符号时总是能找到离自己作用域最近的符号的定义,实现了隐藏某些作用域的效果。

定义在同一个作用域内的符号会通过sibling来形成链,通过elems来保存链表的头部,nelems来保存作用域内存储的符号总数。这两个属性的定义如下: 

/** A linear list that also contains all entries in
 *  reverse order of appearance (i.e later entries are pushed on top).
 */
public Entry elems;

/** The number of elements in this scope.
 * This includes deleted elements, whose value is the sentinel.
 */
int nelems = 0;

由于作用域可以嵌套,所以通过next属性来引用上一个嵌套的作用域,并且每个作用域都有所属的符号owner,属性定义如下: 

/** Next enclosing scope (with whom this scope may share a hashtable)
 */
public Scope next;

/** The scope's owner.
 */
public Symbol owner;

为了节省符号表空间,存储符号的table数组还可能共享,也就是不同的作用域使用同一个table数组,所以通过shared属性来表示,如果当前scope的next所指向的scope的shared属性为1,表示next所指向的Scope作用域与当前作用域共享同一个table数组。这个属性的定义如下:

/** The number of scopes that share this scope's hash table.
 */
private int shared; // 是个私有变量

  

2.2 Scope重要方法的介绍 

(1)enter()方法

 

 

(2)leave()方法

 

 

(3)lookup()方法 

 

检查符号定义的唯一性(比如方法或者是变量) 

 

2.3 实例分析

 

 

 

 

3、类型的表示 

 

第六章-Javac符号表_第2张图片

 

 

 

4、符号的访问(访问者模式)

 

 

 

 

 

 

 

 

 

 

 

 

 

你可能感兴趣的:(第六章-Javac符号表)