編譯器設計-符號表-中間代碼生成

編譯器設計-符號表-中間代碼生成

Compiler Design - Symbol Table

Compiler - Intermediate Code Generation

一.Compiler Design - Symbol Table

符號表是編譯器爲存儲變量名、函數名、對象、類、接口等各種實體的出現情況而創建和維護的一種重要的數據結構。符號表既可用於編譯器的分析部分,也可用於編譯器的綜合部分。
符號表可用於以下目的,具體取決於所使用的語言:

將所有實體的名稱以結構化形式存儲在一個位置。

以驗證是否已聲明變量。

要實現類型檢查,請驗證源代碼中的賦值和表達式在語義上是否正確。

確定名稱的作用域(作用域解析)。

符號表只是一個可以是線性表或哈希表的表。它以以下格式爲每個名稱維護一個條目:

<symbol name, type, attribute>

例如,如果符號表必須存儲有關以下變量聲明的信息:

static int interest;

然後它應該存儲條目,例如:

<interest, int, static>

attribute子句包含與名稱相關的條目。

實施Implementation

如果編譯器要處理少量數據,那麼符號表可以實現爲無序列表,這很容易編碼,但它只適用於小表。符號表可以通過以下方式之一實現:

線性(排序或未排序)列表

二叉搜索樹

哈希表

其中,符號表主要實現爲哈希表,其中源代碼符號本身被視爲哈希函數的鍵,返回值是關於符號的信息。

操作Operations

符號表(線性或哈希)應提供以下操作。

插入()

此操作在分析階段使用得更頻繁,即編譯器的前半部分,其中標識了標記並將名稱存儲在表中。此操作用於在符號表中添加有關源代碼中出現的唯一名稱的信息。存儲名稱的格式或結構取決於手頭的編譯器。

源代碼中符號的屬性是與該符號關聯的信息。此信息包含有關符號的值、狀態、範圍和類型。函數的作用是:將符號及其屬性作爲參數,並將信息存儲在符號表中。

For example:

int a;

應該由編譯器處理爲should be
processed by the compiler as:

insert(a, int);查找()
lookup()操作用於搜索符號表中的名稱以確定:
如果符號存在於表中。
如果在使用前聲明。
如果在作用域中使用了該名稱。
如果符號已初始化。
如果符號聲明瞭多次。
lookup(symbol)
lookup()函數的格式因編程語言而異。基本格式應符合以下要求:如果符號表中不存在該符號,則此方法返回0(零)。如果符號存在於符號表中,則返回存儲在表中的其屬性。
範圍管理
編譯器維護兩種類型的符號表:一種全局符號表,它可以被所有過程訪問,另一種是爲程序中的每個作用域創建的作用域符號表。
爲了確定名稱的範圍,符號表按層次結構排列,如下例所示:
. . .
int value=10;
void pro_one()
{
int one_1;
int one_2;
{
\ int one_3;
|_ inner scope 1 int one_4; |
}
/ int one_5;
{
\ int one_6;
|_ inner scope 2 int one_7; |
}
/ }
void pro_two()
{ int two_1;
int two_2;
{
\ int two_3;
|_ inner scope 3
int two_4; |
}
/ int two_5;
}.
. .
上述程序可以用符號表的層次結構表示:
在這裏插入圖片描述
全局符號表包含一個全局變量(int值)的名稱和兩個過程名稱,這些名稱應該對上面顯示的所有子節點都可用。pro_one symbol表(及其所有子表)中提到的名稱不適用於pro_two symbol及其子表。
此符號表數據結構層次結構存儲在語義分析器中,當需要在符號表中搜索名稱時,將使用以下算法進行搜索:
首先在當前範圍內搜索一個符號,即當前符號表。
如果找到名稱,則搜索完成,否則將在父符號表中搜索,直到,
找到該名稱或已在全局符號表中搜索該名稱。

二.Compiler - Intermediate Code Generation

一個源代碼可以直接翻譯成它的目標機器代碼,那麼我們爲什麼要把源代碼翻譯成一箇中間代碼,然後再翻譯成它的目標代碼呢?讓我們看看需要中間代碼的原因。
在這裏插入圖片描述
如果編譯器在沒有生成中間代碼的選項的情況下將源語言轉換爲目標機器語言,那麼對於每臺新機器,都需要一個完整的本機編譯器。
中間代碼通過保持所有編譯器的分析部分相同,消除了對每臺唯一計算機使用新的完整編譯器的需要。
編譯器的第二部分,synthesis,是根據目標機器而改變的。
通過在中間代碼上應用代碼優化技術,可以更容易地應用源代碼修改來提高代碼性能。中間表示法Intermediate Representation
中間代碼可以用多種方式表示,它們有自己的優點。
高級別的IR-高級別的中間代碼表示非常接近源語言本身。它們可以很容易地從源代碼生成,我們可以很容易地應用代碼修改來提高性能。但對於目標機優化,則不太可取。
低級別的IR-這是一個接近目標機器,這使得它適合於寄存器和內存分配,指令集選擇等。這是一個很好的機器相關的優化。
中間代碼可以是特定於語言的(例如,Java的字節碼)或獨立於語言的(三地址碼)。三地址碼 Three-Address Code
中間代碼生成器以帶註釋的語法樹的形式接收來自其前一階段語義分析器的輸入。然後,可以將該語法樹轉換爲線性表示法,例如後綴表示法。中間代碼往往是與機器無關的代碼。因此,代碼生成器假設有無限數量的內存存儲(寄存器)來生成代碼。

For example:

a = b + c * d;中間代碼生成器將嘗試將此表達式劃分爲子表達式,然後生成相應的代碼。r1 = c * d;r2 = b + r1;a = r2r用作目標程序中的寄存器。
一個三地址碼最多有三個地址位置來計算表達式。三地址碼可以用兩種形式表示:四位和三位。
四倍Quadruples
四位數表示中的每條指令分爲四個字段:operator、arg1、arg2和result。以上示例以四位數格式表示如下:
在這裏插入圖片描述
三元組
三元組表示中的每條指令都有三個字段:op、arg1和arg2。各子表達式的結果由表達式的位置表示。三元組表示與DAG和語法樹的相似性。它們在表示表達式時等價於DAG。
在這裏插入圖片描述
三元組在優化時面臨代碼不可移動的問題,因爲結果是位置的,更改表達式的順序或位置可能會導致問題。
間接三元組
此表示是對三元組表示的增強。它使用指針而不是位置來存儲結果。這使優化器能夠自由地重新定位子表達式以生成優化的代碼。
聲明 Declarations
變量或過程在使用之前必須聲明。聲明涉及內存空間的分配以及符號表中類型和名稱的輸入。一個程序的編碼和設計可以考慮到目標機器的結構,但可能並不總是能夠準確地將源代碼轉換成目標語言。
將整個程序作爲過程和子過程的集合,就可以聲明過程的所有本地名稱。內存分配是以連續的方式完成的,名稱是按照在程序中聲明的順序分配給內存的。我們使用offset變量並將其設置爲零{offset=0},表示基址。
源程序設計語言和目標機器體系結構的名稱存儲方式可能不同,因此使用相對尋址。當從內存位置0{offset=0}開始分配第一個名稱時,後面聲明的下一個名稱應該在第一個名稱旁邊分配內存。
例子:
我們以C語言爲例,其中一個整數變量被分配2字節的內存,一個浮點變量被分配4字節的內存。
int a;
float b;
Allocation process:
{
offset = 0
}
int a;
id.type = int id.width = 2 offset = offset + id.width {offset = 2}
float b;
id.type = float id.width = 4 offset = offset + id.width {offset = 6}
要在符號表中輸入此詳細信息,可以使用過程enter。該方法可以具有以下結構:
enter(name, type, offset)
此過程應在符號表中爲變量名創建一個條目,將其類型設置爲類型,並在其數據區域中設置相對地址偏移量。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章