第113集:符号表(二)

核心知识点讲解

作用域处理

作用域是指变量、函数等符号的可见范围。在编译器中,作用域处理是符号表的核心功能之一。常见的作用域类型包括:

  1. 全局作用域:整个程序都可见
  2. 局部作用域:仅在函数或代码块内可见
  3. 类作用域:仅在类内部可见
  4. 命名空间作用域:在特定命名空间内可见

嵌套作用域

嵌套作用域是指一个作用域可以包含另一个作用域。在嵌套作用域中,内部作用域可以访问外部作用域的符号,但外部作用域不能访问内部作用域的符号。

例如:

int global_var;  // 全局作用域

void function() {
    int local_var;  // 函数作用域
    
    if (condition) {
        int block_var;  // 块作用域
        // 可以访问 block_var, local_var, global_var
    }
    // 可以访问 local_var, global_var
    // 不能访问 block_var
}
// 只能访问 global_var

符号的查找

在嵌套作用域中,符号查找遵循从内向外的原则:

  1. 首先在当前作用域中查找
  2. 如果找不到,向上层作用域查找
  3. 直到找到符号或到达全局作用域
  4. 如果全局作用域也找不到,则报告未声明错误

这种查找方式确保了:

  • 内部作用域可以覆盖外部作用域的同名符号
  • 符号的可见性符合程序设计的预期

实现代码

嵌套作用域的符号表实现

为了处理嵌套作用域,符号表通常采用层次结构的实现方式:

  1. 栈式符号表

    • 每个作用域对应栈中的一个符号表
    • 进入作用域时压栈
    • 退出作用域时弹栈
    • 查找时从栈顶向栈底搜索
  2. 层次符号表

    • 每个作用域有自己的符号表
    • 每个符号表有指向父作用域符号表的指针
    • 查找时从当前符号表开始,沿父指针链向上搜索

实用案例分析

栈式符号表实现示例

class Symbol:
    def __init__(self, name, type_):
        self.name = name
        self.type = type_

class Scope:
    def __init__(self):
        self.symbols = {}

class SymbolTable:
    def __init__(self):
        self.scopes = []
        self.enter_scope()  # 全局作用域
    
    def enter_scope(self):
        """进入新作用域"""
        self.scopes.append(Scope())
    
    def exit_scope(self):
        """退出当前作用域"""
        if len(self.scopes) > 1:  # 保留全局作用域
            self.scopes.pop()
    
    def insert(self, name, type_):
        """在当前作用域插入符号"""
        current_scope = self.scopes[-1]
        if name in current_scope.symbols:
            return False  # 重复声明
        current_scope.symbols[name] = Symbol(name, type_)
        return True
    
    def lookup(self, name):
        """从当前作用域向外查找符号"""
        for scope in reversed(self.scopes):
            if name in scope.symbols:
                return scope.symbols[name]
        return None
    
    def lookup_current_scope(self, name):
        """仅在当前作用域查找符号"""
        current_scope = self.scopes[-1]
        return current_scope.symbols.get(name)

符号表使用示例

# 使用符号表处理变量声明和使用
st = SymbolTable()

# 全局作用域
st.insert("global_var", "int")

# 进入函数作用域
st.enter_scope()
st.insert("local_var", "float")

# 进入块作用域
st.enter_scope()
st.insert("block_var", "char")

# 查找符号
print(st.lookup("block_var"))  # 找到: char
print(st.lookup("local_var"))  # 找到: float
print(st.lookup("global_var"))  # 找到: int
print(st.lookup("nonexistent"))  # None

# 退出块作用域
st.exit_scope()
print(st.lookup("block_var"))  # None (已退出作用域)
print(st.lookup("local_var"))  # 找到: float

# 退出函数作用域
st.exit_scope()
print(st.lookup("local_var"))  # None (已退出作用域)
print(st.lookup("global_var"))  # 找到: int

处理函数参数

def process_function_definition(st, func_name, return_type, params):
    # 进入函数作用域
    st.enter_scope()
    
    # 插入参数到符号表
    for param_name, param_type in params:
        st.insert(param_name, param_type)
    
    # 处理函数体...
    
    # 退出函数作用域
    st.exit_scope()
    
    # 在全局作用域插入函数信息
    st.insert(func_name, f"function({', '.join(t for _, t in params)}) -> {return_type}")

# 使用示例
st = SymbolTable()
process_function_definition(st, "add", "int", [("a", "int"), ("b", "int")])
print(st.lookup("add"))  # 找到函数信息
print(st.lookup("a"))  # None (参数在函数作用域内)

小结

符号表的作用域处理是编译器实现中的重要部分:

  • 嵌套作用域:支持代码块、函数、类等不同级别的作用域
  • 从内向外查找:确保符号的正确解析和覆盖规则
  • 栈式或层次结构:有效管理不同作用域的符号
  • 作用域进入/退出:正确处理作用域的开始和结束

通过合理的符号表设计,可以有效地处理复杂的作用域规则,为编译器的语义分析和后续阶段提供可靠的符号信息。

« 上一篇 符号表(一) 下一篇 » 符号表(三)