當前位置:成語大全網 - 新華字典 - 如何建立符號表

如何建立符號表

Symbol Tables

為了維持靜態作用域的程序裏各個名字的軌跡,編譯器需要依靠壹種稱為符號表的數據結構。從最基本的層次上看,符號表就是壹個字典:它把名字映射到編譯器已知的有關信息。這裏最基本的操作是把壹個新映射關系(名字對象約束)放入表裏,以及(非破壞性的)用壹個給定名字去提取映射下的信息,以後我們把

這兩個操作分別稱為insert和lookup。大部分語言裏的靜態作用域規則還提出了另外的復雜性,它們要求在程序裏的不同部分有不同的引用環境。為了處理作用域規則,我們可能希望簡單增加壹個remove操作。由於編譯器在語義分析階段要從頭到尾掃描代碼,這樣它就可以在某個作用域開始時插入新約束,在作用域最後撤銷它們。但是,存在壹些因素使這種直接做法並不實際。

¨ 在許多有著嵌套作用域的語言裏,內層聲明的效果可以遮蔽外層聲明,這就意味著符號表必須有能力為壹個給定名字保存任意數目的映射。lookup操作必須返回最內層的映射,到作用域結束時還必須使外層映射重新變成可見的。

¨ 類Algol語言裏的記錄(結構)具有某種作用域性質,但卻又不享有作用域那樣的良好嵌套結構。當語義分析器看到壹個記錄聲明時,它就必須記下各個記錄域的名字(它們也是遞歸的,因為記錄可以嵌套)。在這種聲明結束時,各個域的名字又必須變成不可見的。然而,在此之後,壹旦這壹記錄類型的某個變量出現在程序的正文裏(例如在my_rec.field_name),在引用中位於圓點之後的部分,這些域名又必須立即重新變成可見的。在Pascal和另壹些有with語句的語言裏,記錄域的名字還應該在多個語句的上下文裏變成可見的。

¨ 某些時候壹些名字有可能在它們被聲明之前使用,即使在類Algol語言裏情況也如此。舉例說,Algol 60和Algol 68都允許標號的向前引用。Pascal避免了這種情況,它要求標號必須在作用域開始處聲明,但還是允許指針聲明的向前引用:

type

company = record

CEO : ^person; (* forward reference *)

...

end;

person = record

employer : ^company;

...

end;

¨ Pascal和其他語言都允許子程序的向前聲明,以便支持相互遞歸:

procedure Q (A, B : integer); forward;

procedure P (A, B : integer);

begin

...

Q (3, 4);

...

end;

procedure Q; (* parameters are not repeated in Pascal *)

begin

...

P (4, 5);

...

end;

在看到這段代碼裏的向前聲明時,語義分析器必須記住Q的參數,以便後面可以在Q的體裏使它們重新變成可見的,但在此期間又必須使它們成為不可見的。這種操作類似於記住記錄域的名字。

¨ 雖然我們有可能希望在作用域結束時忘記有關的名字,甚至回收這些名字在符號表裏占據的空間,但有關它們的信息仍需要保存起來,以便符號糾錯系統(symbolic debugger)使用。這種糾錯系統是非常有用的工具,用戶可以借助它方便地操縱程序,如啟動程序,停住它,讀出或者修改程序裏的數據等等。為了分析來自用戶的高級名字(例如,要求打印出my_firm^.revenues[1999] 的值),符號糾錯程序必須能訪問編譯器的符號表。為了使符號表在運行時也可以用,編譯器通常會把這個表保存到最後的機器語言程序裏的某個隱蔽的部分。

靜態作用域的大部分變化都可以通過擴充基本符號表的方式處理,通過增加壹對enter_scope和leave_scope操作維持可見性的軌跡。任何東西都不會從符號表裏刪除,在整個編譯階段所有的結構都保留著,最後還要為糾錯系統使用而保存起來。帶有可見性處理的符號表可以以多種不同方式實現,下面描述的方式歸功於LeBlanc和Cook [CL83]。

在遇到每個作用域時賦給它壹個序列號。給定最外層的作用域(其中包含著預定義的標識符)編號0,包含用戶定義全局名字的作用域給以編號1。其他作用域按遇到它們的順序進行編號。所有的編號互不相同,它們並不表示詞法嵌套的層次,但也有壹點,嵌套於內部的子程序的編號自然會大於它們的外圍作用域的編號。

所有的名字都被放入壹個大的散列表裏,以名字作為關鍵碼,無論其作用域如何。表裏的每項都包含壹個符號名,其類屬(變量、常量、類型、過程、域名字、參數等等),作用域編號,類型(壹個指向另壹符號表項的指針),以及另壹些特定類屬所擁有的信息。

除了這壹散列表之外,符號表還包含壹個作用域堆棧,它按順序指明組成當前引用環境的所有作用域。在語義分析器掃描程序的過程中,在進入或退出程序時分別壓入或者彈出這個堆棧。作用域堆棧的項裏包含著作用域編號,指明這壹作用域是否為閉的,有些情況下還可以有另外壹些信息。

圖3.13 LeBlanc-Cook符號表的lookup算法。

當需要到表裏查找名字時,我們會順著某個適當的散列表鏈向下找,這樣就會找到要找的名字所對應的壹些項。對於每個匹配項,我們都向下掃描作用域堆棧,看看這個項所在的作用域是否可見。這種堆棧查看的深度不應超過最上面的閉作用域。要把導入項和導出項變為在它們的作用域之外可見的,方法就是在表裏建立另外的項,讓這些項裏包含著指向實際項的指針。對於所有帶有作用域編號0的項,我們都不需要去檢查作用域堆棧,因為它們是滲透性的。圖3.13裏是lookup算法的偽代碼。

圖3.14的右下角是壹個Modula-2程序的梗概,圖中其余部分展現的是在過程P2裏with語句處的引用環境的符號表配置情況。作用域堆棧裏包含4個項,分別表示那個with語句,過程P2,模塊M和全局作用域。with語句的作用域指明了在這壹特定作用域裏的(域)名字屬於哪個記錄變量。最外面的滲透性作用域沒有顯式表示。

圖3.14 壹個Modula-2例子程序的LeBlanc-Cook符號表。作用域堆棧表示在過程P2裏with語句的引用環境。為清楚起見,許多指向符號表裏對應於integer和real的項都用帶括號的 (1) 和 (2) 表示,沒有畫出箭頭。

因為這裏的散列表以名字作為關鍵碼,特定名字的所有項都出現在同壹個散列鏈裏。在這個例子裏,散列沖突導致A2、F2和T出現在同壹個鏈裏。變量V和I(M的I)有另外的項,使它們跨過閉作用域M的邊界後仍為可見的。當我們處於P2裏時,對於I的查找操作將找到P2的I,M的I裏的兩個項都不可見。類型T的項指明了在with語句期間放入作用域堆棧的作用域編號。每個子程序的項裏包含了壹個頭指針,指向子程序參數的鏈接表,以便做調用分析時使用(圖中沒有給出這些鏈的壹些鏈接)。在代碼生成過程中,許多符號表項還可能包含另外的域,表示例如對象大小和運行時地址等等信息。

圖片信息,看參考資料。。。