【編譯原理系列】變數與過程翻譯
阿新 • • 發佈:2021-01-30
宣告語句的翻譯
宣告語句的作用是為可執行語句提供資訊,以便於其執行;對宣告語句的處理,主要是將所需要的資訊正確地填寫進合理組織的符號表中
變數的宣告
- 型別定義:為編譯器提供儲存空間大小的資訊(預定義&自定義)
- 變數宣告:為變數分配儲存空間
組合資料的型別定義和變數宣告:
- 定義與宣告在一起,定義與宣告分離
決定變數儲存空間的是變數的資料型別
- 定義確定儲存空間,宣告分配儲存空間
- 簡單資料型別的儲存空間是已經確定的,如integer可以佔4個位元組,real可以佔8個位元組,char可以佔1個位元組等
- 組合資料型別變數的儲存空間,需要編譯器根據程式設計師提供的資訊計算而定
定義就好像typedef struct node{};
,宣告就好像struct node Node;
,使用就好像Node.val=1;
語法制導翻譯
- 全程量offset:記錄當前符號儲存的偏移量,初值設為0
- 屬性.type和.width:變數的型別和所佔據的儲存空間
- 過程enter(name, type, offset):為type型別的變數name建立符號表條目,併為其分配儲存空間(位置)offset
產生式: 語義規則: (1)D→D;D (2)D→id:T {enter(id.name, T.type, offset); offset:=offset+T.width;} (3)T→int {T.type:=integer; T.width:=4;} (4)T→real {T.type:=real; T.width:=8;} (5)T→array [num] of T1 {T.type:=array(num.val, T1.type); T.width:=num.val*T1.width;} (6)T→^T1 {T.type:=pointer(T1.type); T.width:=4;}
左值與右值
形式上
- 出現在賦值號左邊和右邊的變數分別稱為左值和右值;
實質上,
- 左值必須具有儲存空間,右值可以僅是一個值,而沒有儲存空間;
- (變數【簡單變數、組合變數】是左值,左值是地址,右值是值)形象地講,左值是容器,右值是內容
過程的定義與宣告
過程(procedure):
- 過程頭/規格說明(做什麼)+過程體(怎麼做);(有返回值的也稱為函式,被作業系統呼叫的過程稱為主程式)
過程的三種形式: 過程定義、過程宣告和過程呼叫。
- 過程定義:過程頭+過程體;
- 過程宣告:過程頭
先聲明後引用的原則,若在引用前已定義,則宣告可省略,因為定義已包括了宣告
引數的傳遞
1、形參與實參
- 定義時的引數稱為形參(parameter或formal parameter)
- 引用時的引數稱為實參(argument或actual parameter),實在引數
2、常見的引數傳遞形式:(不同的語言提供不同的形式)
-
值呼叫(call by value)
- 過程內部對引數的修改,不影響作為實參的變數原來的值
- 任何可以作為右值的物件均可作為實參
- 過程定義時形參被當作區域性名看待,並在過程內部為形參分配儲存單元
- 呼叫過程前,首先計算實參並將值(實參的右值)放入形參的儲存單元
- 過程內部對形參單元中的資料直接訪問
-
引用呼叫(call by reference)
-
過程內部對形參的修改,等價於直接對實參的修改
-
實參必須是左值
-
定義時形參被當作區域性名看待,並在過程內部為形參分配儲存單元
-
呼叫過程前,將作為實參的變數的地址(左值)放進形參的儲存單元
-
過程內把形參單元中的資料當作地址,間接訪問
-
存在副作用
int a=2; void add_one(int &x){ a=x+1; x=x+1; } void main () { cout<<"before: a="<<a<<endl;//2 add_one(a); cout<<"after: a="<<a<<endl;//4 }
-
-
複寫-恢復(copy-in/copy-out)
- 實參與非本地量共用一個儲存空間,使得在過程內改變引數值的同時,也改變了非本地量的值
- 值呼叫和引用呼叫的結合
- 過程內對引數的修改不直接影響實參,避免了副作用
- 返回時將形參內容恢復給實參,實現了引數的返回
- 實參必須是左值
- 過程定義時形參被當作區域性名看待,並在過程內部為形參分配單元(複寫)
- 呼叫過程前,首先計算實參並將值(實參的右值)放入形參的儲存單元
- 過程內部對形參單元中的資料直接訪問
- 過程返回前將形參的右值放回實參的儲存單元(恢復)
-
換名呼叫(call by name)
- 過程被認為巨集,每次對過程的呼叫,實質上是用過程體替換過程呼叫,替換中用實參的文字替換體中的形參;這樣的替換方式被稱為巨集替換或巨集展開
- 當需要保持實參的完整性時, 可以為實參加括弧
- 在c++中的形式是巨集定義
#define
【一種折中的方法,c++的內斂函式inline,避免了函式呼叫的同時,也消除了巨集替換的副作用】 - 執行速度快
3、引數傳遞方法的實質:
- 實參是代表左值、右值、還是實參本身的正文
過程的作用域
同樣遵守的是靜態作用域和最近巢狀原則
- 設主程式(最外層過程)的巢狀深度dmain=1,
- <1> 若過程A直接巢狀定義過程B,則dB=dA+1;
- <2> 變數宣告時所在過程的巢狀深度,被認為是該變數的巢狀深度
巢狀過程
- 名字作用域資訊的儲存,可以用具有巢狀結構的符號表來實現,每個過程可以被認為是一個子符號表,或者是符號表中的一個節點
- 巢狀的節點之間可以用雙向的連結串列連線,正向的鏈指示過程的巢狀關係,而逆向的鏈可以用來實現按作用域對名字的訪問
語法制導翻譯
P → D (1)
D → D ; D (2)
| id : T (3)
| proc id ; D; S (4)
修改文法,使得在定義D之前生成符號表,LR分析
P → M D (1)
D → D ; D (2)
| id : T (3)
| proc id ; N D; S (4)
M →ε (5)
N →ε (6)
全程量:有序對棧(tblptr, offset)
- 其中, tblptr儲存指向符號表節點的指標,
- offset儲存當前節點所需寬度。
棧幀的組成
- 對於巢狀過程
- 動態鏈:指向本過程的呼叫過程的活動記錄的起始地址,也稱控制鏈。
- 靜態鏈:指向本過程的直接外層過程的活動記錄的起始地址,也稱存取鏈。
棧上的操作: push(t, o)、pop、top(stack)
- 函式mktable(previous):建立一個新的節點,並返回指向新節點的指標;引數previous是逆向鏈,指向該節點的前驅,或者說是外層
- 過程enter(table, name, type, offset):在table指向的節點中為名字name建立新的條目,包括名字的型別和儲存位置等
- 過程addwidth(table, width):計算table節點中所有條目的累加寬度,並記錄在table的頭部資訊中
- 過程enterproc(table, name, newtable):為過程name在table指向的節點中建立一個新的條目;引數newtable是正向鏈,指向name過程自身的符號表節點
產生式: 語義規則:
(1) P → M D {addwidth(top(tblptr),top(offset)); pop;}
(2) M → ε {t:=mktable(null); push(t, 0,);}
(3) D → D ; D
(4) D → id : T {enter(top(tblptr),id.name,T.type,top(offset));
top(offset):=top(offset)+T.width;}
(5) D → proc id ; N D1; S { t:=top(tblptr);
addwidth(t, top(offset));
pop;
enterproc(top(tblptr), id.name, t);
}
(6) N → ε {t:=mktable(top(tblptr)); push(t,0);}
序號 產 生 式 語 義 處 理 結 果
(1) M1→ε t1 := mktable(null); push(t1, 0);
(2) N1→ε t2 := mktable(top(tblptr)); push(t2, 0);
(3) T1→int T1.type=integer, T1.width=4
(4) T2→array [10]of T2 T2.type=array(10,in…≥t), T2.width=40
(5) D1→a:T2 (a,arr,0)填進t2所指節點,top(offset):=40
(6) T3→int T3.type=integer, T3.width=4
(7) D2→x:T3 (x,int,40)填進t2所指節點 top(offset):=44
(8) N2→ε t3:=mktable(top(tblptr)); push(t3,0);
(9) T4→int T4.type=integer, T4.width=4
(10) D3→i:T4 (i,int,0)填進t3所指節點,top(offset):=4
(11) D4→proc readarray N2 D3 ; S t:=top(tblptr); addwidth(t,top(offset)); pop; enterproc(top(tblptr),readarray,t);
(12) D7→proc sort N1 D6 ; S t:=top(tblptr); addwidth(t,top(offset)); pop;
enterproc(top(tblptr),sort,t);
(13) P→M1 D7 addwidth(top(tblptr),top(offset)); pop;