1. 程式人生 > 其它 >【編譯原理系列】變數與過程翻譯

【編譯原理系列】變數與過程翻譯

技術標籤:# 編譯原理編譯原理

宣告語句的翻譯

宣告語句的作用是為可執行語句提供資訊,以便於其執行;對宣告語句的處理,主要是將所需要的資訊正確地填寫進合理組織的符號表

變數的宣告

  • 型別定義:為編譯器提供儲存空間大小資訊(預定義&自定義)
  • 變數宣告:為變數分配儲存空間

組合資料的型別定義和變數宣告:

  • 定義與宣告在一起,定義與宣告分離

決定變數儲存空間的是變數的資料型別

  1. 定義確定儲存空間,宣告分配儲存空間
  2. 簡單資料型別的儲存空間是已經確定的,如integer可以佔4個位元組,real可以佔8個位元組,char可以佔1個位元組等
  3. 組合資料型別變數的儲存空間,需要編譯器根據程式設計師提供的資訊計算而定

定義就好像typedef struct node{};,宣告就好像struct node Node;,使用就好像Node.val=1;

語法制導翻譯

  1. 全程量offset:記錄當前符號儲存的偏移量,初值設為0
  2. 屬性.type和.width:變數的型別和所佔據的儲存空間
  3. 過程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)

  1. 函式mktable(previous):建立一個新的節點,並返回指向新節點的指標;引數previous是逆向鏈,指向該節點的前驅,或者說是外層
  2. 過程enter(table, name, type, offset):在table指向的節點中為名字name建立新的條目,包括名字的型別和儲存位置等
  3. 過程addwidth(table, width):計算table節點中所有條目累加寬度,並記錄table的頭部資訊
  4. 過程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;