符号表和语义分析
一、语义分析的内容
1. 遇到名称 (变量名,函数名) 定义时
检查是否重定义。(redefined)
2. 遇到名称使用时
检查是否未定义。(undefined)
3. 类型检查
表达式中的运算,赋值,函数调用中的参数,都要检查类型是否匹配或相容。
二、符号表
为了实现语义分析,使用符号表。
当定义一个名称时,需要查询符号表,看该名称是否重定义。若是,报错 redefined;否则把该名称插入到符号表中。
当使用一个名称时,需要查询符号表,看该名称是否未定义。若是,报错 undefined。
为简单起见,用链表实现符号表。产品级编译器中,通常使用hash表来实现符号表。
三、作用域 (scope)
为了减少名称的冲突,编程语言中使用作用域的概念。作用域指名称(变量名,函数名等)的有效引用范围。
在不同作用域中,可定义相同的名称。在内层作用域中,可定义与外层作用域相同的名称。
四、C语言中的作用域
有三种:
1. 全局作用域
1) 全局变量
2) 函数
函数内不能定义函数,所有函数是全局的。从定义处直到文件结束的范围内,函数有效、可以引用。
2. 函数作用域
包括函数参数和函数内定义的局部变量。只在该函数范围内有效。
3. 块作用域。
在块语句(花括号)内定义的变量。只在该块语句范围内有效。
示例。
1 #include <stdio.h>
2 // 进入全局作用域:level = 0
3 int a, b;
4
5 int add(int a, int b) { //从参数表开始,进入函数作用域:level = 1
6 int c;
7 c = a + b;
8 return c;
9 } // 退出函数作用域:level = 0
10
11 int main() { // 从参数表开始,进入函数作用域:level = 1
12 int a, c;
13 a = 90; b = 20;
14 c = add(a, b) / b;
15
16 if ( a > b )
17 { // 进入块作用域:level = 2
18 int c = a;
19 while ( c > b )
20 c = c - b;
21 a = c;
22 } // 退出块作用域:level = 1
23
24 printf("a=%d b=%d c=%d\n", a, b, c);
25 } // 退出函数作用域:level = 0
五、最内嵌套作用域规则
在外层作用域定义的名称,在内层作用域里可见,除非内层作用域里定义了相同的名称。
在使用一个名称时,按照从内层到外层的顺序,来查找该名称的定义之处。
满足这种规则的作用域,称静态作用域,也称词法作用域。
六、符号表的实现
用链表实现。每次插入符号,插入到表头。
设置一个全局变量 level,记录当前作用域的层次 (深度)。
用一例加以说明。
$ cat -n t4.cmm
1 int a, b;
2
3 int add(int a, int b) {
4 int c;
5 c = a + b;
6 return c;
7 }
8
9 int main() {
10 int a, y;
11 a = 100; b= 20;
12 y = a / b;
13 print y;
14 }
1) 一开始,进入全局作用域, level = 0;
2) 分析到一个函数,从参数表开始,进入函数作用域,level++;
分析完第4行后,符号表:
3) 每退出一个函数,释放该函数加入到符号表的符号,level--;
分析完第7行后,符号表:(一个函数结束后,)
4) 进入下一个函数
分析完第10行后,符号表: