名字与绑定要一起理解
程序里的同一拼写可以出现在不同作用域,并分别指向不同绑定。符号表需要记录的是某个位置的名字代表什么,而不是把源文件中所有同名文本都当成一个对象。
Python 编译器在抽象语法树之后、字节码生成之前建立符号表,计算标识符的作用域。公开接口可以区分参数、局部变量、全局变量、自由变量及导入等信息。
名字分类有时依据整个函数,而不按程序执行到哪一行临时决定。Python函数里若对x赋值,且没有相应global或nonlocal声明,函数中的x会被视为局部名字;在赋值之前读取它,会触发UnboundLocalError,即使外层已有同名值。编译阶段扫描绑定位置,正是符号信息的一项用途。外层值是否存在,与当前这处读取究竟被分类为哪个作用域,是需要先分清的两步。
为代码生成找到已有值
LLVM 的入门编译器用 NamedValues 把当前作用域中的名字映射到已有的中间表示值。生成变量表达式时,先在表里查找;不存在的名字不能直接被当成一个合法的新值。
教学例子只支持有限的绑定形式。真实编译器还要处理嵌套作用域、遮蔽、模块及类型信息,因此“用一张映射表”说明了基本职责,并没有穷尽所有实现。
参考:[2]
目标文件中的另一层符号
经过编译以后,目标文件还可能保存供链接使用的符号。GNU nm 显示这些符号的值与类别,并可区分定义、未定义引用、代码或数据等情况,便于检查跨文件连接。
这类输出并不是原源程序全部名字的清单:编译与优化会改变需要保留的内容,文件格式也有自己的类别规则。排查名字问题时,应先确认正在查看的是哪个阶段的符号信息。
参考:[3]