編譯原理 符合表和語法分析
符號表和語義分析
一、語義分析的內容
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行後,符號表: