目錄 |
---|
01 文法和語言、詞法分析複習 |
02 自頂向下、自底向上的LR分析複習 |
03 語法制導翻譯和中間代碼生成複習 |
04 符號表、運行時存儲組織和代碼優化複習 |
符號表須要在編譯期間用到,記錄符號的具體信息。本部分只討論PL/0符號表的創建。html
PL/0的符號表包含5個信息:編程
例以下面的程序:數組
const a = 35, b = 49; var c, d, e; procedure p; var g;
對應的符號表爲:函數
NAME | KIND | VAL/LEVEL | ADD | SIZE |
---|---|---|---|---|
a | CONSTANT | 35 | ||
b | CONSTANT | 49 | ||
c | VARIABLE | LEV | DX | |
d | VARIABLE | LEV | DX+1 | |
e | VARIABLE | LEV | DX+2 | |
p | PROCEDURE | LEV | p的入口地址 | 4 |
g | VARIABLE | LEV+1 | DX |
又例以下面的程序:優化
const a = 25; var x, y; procedure p; var z; begin ... end; procedure r; var x, s; procedure t; var v; begin ... end; begin ... end; begin ... end.
對應的符號表爲:spa
NAME | KIND | VAL/LEVEL | ADD | SIZE |
---|---|---|---|---|
a | CONSTANT | 25 | ||
x | VARIABLE | LEV | DX | |
y | VARIABLE | LEV | DX+1 | |
p | PROCEDURE | LEV | p的入口地址 | 4 |
z | VARIABLE | LEV+1 | DX | |
r | PROCEDURE | LEV | r的入口地址 | 5 |
x | VARIABLE | LEV+1 | DX | |
s | VARIABLE | LEV+1 | DX+1 | |
t | PROCEDURE | LEV+1 | t的入口地址 | 4 |
v | VARIABLE | LEV+2 | DX |
PL/0程序運行時,每一次過程調用都將在運行棧增長一個過程活動記錄。 其中,當前活動記錄的起始單元由基址寄存器b指出,結束單元是棧頂寄存器t所指單元的前一個單元。翻譯
PL/0的過程活動記錄中的頭3個單元是固定的聯繫信息:code
這樣,每當一個過程被調用,就須要在棧上先分配3個空間用來存儲上述信息,而後纔是分配空間存儲過程的局部變量。對於主過程,SL=DL=RA=0。htm
這裏給出一道例題。對於下列程序:blog
var m, n, g:integer; function gcd(m,n:integer):integer; begin if n = 0 then g := m else g := gcd(n, m mod n) end; begin m := 24; n := 16; g := gcd(m, n) end.
它的運行棧爲:
經常使用優化技術有:
\((1)T_1:=4*I\)
\((2)T_2:=addr(A)-4\)
\((3)T_3:=T_2[T_1]\)
\((4)T_4:=4*I\)
\((5)T_5:=addr(B)-4\)
\((6)T_6:=T_5[T_4]\)
能夠看到\((4)\)式作了和\((1)\)式重複的工做,能夠改寫成\(T_4:=T_1\)
原代碼:
塊1
\((1)P:=0\)
\((2)I:=1\)
塊2
\((3)T_1:=4*I\)
\((4)T_2:=addr(A)-4\)
\((5)T_3:=T_2[T_1]\)
\((6)P:=P+T_3\)
\((7)I:=I+1\)
\((8)if \;I<=20\;goto\;(3)\)
能夠看到\((4)\)式在每次循環都作重複的工做,能夠把它提到循環外來,記得修改跳轉:
塊1
\((1)P:=0\)
\((2)I:=1\)
\((3)T_2:=addr(A)-4\)
塊2
\((4)T_1:=4*I\)
\((5)T_3:=T_2[T_1]\)
\((6)P:=P+T_3\)
\((7)I:=I+1\)
\((8)if \;I<=20\;goto\;(4)\)
把強度大的運算換成強度小的運算,好比用加法換乘法:
塊1
\((1)P:=0\)
\((2)I:=1\)
\((3)T_2:=addr(A)-4\)
塊2
\((4)T_1:=4*I\)
\((5)T_3:=T_2[T_1]\)
\((6)P:=P+T_3\)
\((7)I:=I+1\)
\((8)if \;I<=20\;goto\;(4)\)
把\((4)\)式通過處理,並修改跳轉:
塊1
\((1)P:=0\)
\((2)I:=1\)
\((3)T_1:=0\)
\((4)T_2:=addr(A)-4\)
塊2
\((5)T_1:=T_1+4\)
\((6)T_3:=T_2[T_1]\)
\((7)P:=P+T_3\)
\((8)I:=I+1\)
\((9)if \;I<=20\;goto\;(5)\)
下面的代碼中,\(I\)和\(T_1\)保持4倍的線性關係:
塊1
\((1)I:=1\)
\((2)T_1:=4*I\)
塊2
\((3)P:=T_2[T_1]\)
\((4)I:=I+1\)
\((5)T_1=T_1+4\)
\((6)if\;I<=20\;goto\;(3)\)
能夠把循環條件\(I<=20\)改成\(T_1<=80\),而後修改\(T_1\)的初始賦值,這樣\(I\)在整個循環都沒有被用上,能夠剔除:
塊1
\((1)T_1:=4\)
塊2
\((2)P:=T_2[T_1]\)
\((3)T_1=T_1+4\)
\((4)if\;T_1<=80\;goto\;(2)\)
下面的代碼中,在計算\(4*I\)時,\(I\)一定爲1:
\((1)I:=1\)
\((2)T_1:=4*I\)
所以能夠直接在編譯期間算出它的值是4:
\((1)I:=1\)
\((2)T_1:=4\)
看下面的代碼:
塊1
\((1)T_1:=4\)
\((2)I:=1\)
塊2
\((3)T_2:=T_1\)
\(...\)
\((7)T_3:=T_4[T_2]\)
\((8)T_1:=T_1+T_3\)
\((9)I:=I+1\)
\((10)if\;T_1<=80\;goto\;(3)\)
四元式\((3)\)把\(T_1\)的值寫入\(T_2\)中,但\(T_2\)和\(T_1\)的值在\((3)\)到\((7)\)之間沒有發生改變,故將\((7)\)改成\(T_3:=T_4[T_1]\)
此時\((3)\)式沒有被引用,屬於無用賦值,能夠刪掉。
而後,\((2)\),\((9)\)對\(I\)賦值,但也只是自我引用,其他地方沒有須要用到\(I\),屬於無用賦值,故能夠刪掉。
最終變爲:
塊1
\((1)T_1:=4\)
塊2
\(...\)
\((5)T_3:=T_4[T_1]\)
\((6)T_1:=T_1+T_3\)
\((7)if\;T_1<=80\;goto\;(2)\)
一個基本塊內部是順序執行的,故內部不能有任何中止、分支、跳轉。
基本塊的劃分:
例如:
\((1)\quad pi:=3.14\)
\((2)\quad ar:=0.0\)
\((3)\quad n:=16\)
\((4)\quad r:=1\)
\((5)\quad if\;n<=1\;goto\;(9)\)
\((6)\quad r:=r*n\)
\((7)\quad n:=n-1\)
\((8)\quad goto\;(5)\)
\((9)\quad ar:=2*pi\)
\((10)\quad ar:=ar*r\)
\((11)\quad print\;ar\)
通過劃分後:
B1
\((1)\quad pi:=3.14\)
\((2)\quad ar:=0.0\)
\((3)\quad n:=16\)
\((4)\quad r:=1\)
/////////////////////////////////////////////////
B2
\((5)\quad if\;n<=1\;goto\;(9)\)
/////////////////////////////////////////////////
B3
\((6)\quad r:=r*n\)
\((7)\quad n:=n-1\)
\((8)\quad goto\;(5)\)
/////////////////////////////////////////////////
B4
\((9)\quad ar:=2*pi\)
\((10)\quad ar:=ar*r\)
\((11)\quad print\;ar\)
流圖 是在已經劃分基本塊的基礎上,構造一個有向圖。
上面的基本塊集合爲\(\{B1,B2,B3,B4\}\),能夠用有向邊集合\(\{B1\rightarrow B2, B2\rightarrow B3, B3\rightarrow B2, B2\rightarrow B4\}\),這裏不畫圖。
支配結點,指的是對任意兩個結點m和n來講,若是從流圖的首結點出發,到達n的任一通路都要通過m,則稱m是n的支配結點,記爲\(m\;DOM\;n\)
下圖是某個程序的流圖,其結點即程序中的基本塊
全部結點的支配結點集D(n):
\(D(1)=\{1\}\)
\(D(2)=\{1,2\}\)
\(D(3)=\{1,2,3\}\)
\(D(4)=\{1,2,4\}\)
\(D(5)=\{1,2,4,5\}\)
\(D(6)=\{1,2,4,6\}\)
\(D(7)=\{1,2,4,7\}\)
該圖的有向邊集合爲:\(\{1\rightarrow 2, 2\rightarrow 3, 2\rightarrow 4, 3\rightarrow 4, 4\rightarrow 2, 4\rightarrow 5, 4\rightarrow 6, 5\rightarrow 7, 6\rightarrow 6, 6\rightarrow 7, 7\rightarrow 4\}\)
回邊指的是存在一條邊\(A\rightarrow B\),使得\(B\in D(A)\)。故上圖的回邊有\(4\rightarrow 2, 6\rightarrow 6,7\rightarrow4\)
一個循環由其中的一條回邊\(A\rightarrow B\)對應的兩個結點\(B,A\),以及有通路到達\(A\)而不通過\(B\)的全部結點組成,而且保證\(B\)是該循環的惟一入口結點。
如包含回邊\((6\rightarrow 6)\)的循環爲\(\{6\}\)
包含回邊\((7\rightarrow 4)\)的循環爲\(\{4,5,6,7\}\)
包含回邊\((4\rightarrow 2)\)的循環爲\(\{2,3,4,5,6,7\}\)
由於竟然還有編程填空題這種恐怖存在,須要瞭解下面這些內容,否則填空都不知道怎麼填。
能夠用的全局變量以下:
全局變量 | 含義 |
---|---|
sym | 當前讀取到的符號類型 |
num | 當前讀取到的值 |
id | 當前讀取到的標識符名稱 |
cx | 當前中間代碼將被寫入時的索引 |
tx | 當前符號表將被寫入的索引 |
code | 指令數組,類型爲instruction |
在分析控制流的函數能夠用的變量以下:
變量 | 含義 |
---|---|
cx1,cx2 | 分別記錄條件爲真/假時須要跳轉的地址 |
instruction
的結構體以下:
typedef struct { int f; // 函數類別 int l; // 層級 int a; // 地址/當即數/操做類別 } instruction;
函數類別和操做類別以下:
enum opcode { LIT, // 取當即數 OPR, // 操做 LOD, // 讀取 STO, // 保存 CAL, // 調用 INT, // 初始化空間 JMP, // 無條件跳轉 JPC // 有條件跳轉 }; enum oprcode { OPR_RET, OPR_NEG, OPR_ADD, OPR_MIN, OPR_MUL, OPR_DIV, OPR_ODD, OPR_EQU, OPR_NEQ, OPR_LES, OPR_LEQ, OPR_GTR, OPR_GEQ };
符號類別以下:
enum symtype { NUMBER, // 符號類型 PLUS, MINUS, TIMES, SLASH, ODD, EQU, // = NEQ, // <> LES, // < LEQ, // <= GTR, // > GEQ, // >= LPAREN, // ( RPAREN, // ) COMMA, // , SEMICOLON, // ; PERIOD, // . // 關鍵字 BEGINSYM, ENDSYM, IFSYM, THENSYM, WHILESYM, DOSYM, CALLSYM, CONSTSYM, VARSYM, PROCEDURESYM }
能夠用的全局函數以下:
全局函數 | 含義 |
---|---|
getsym | 獲取下一個符號的類型到sym。若是sym是number,則num將會存放值;若是sym是標識符,id將存放標識符名稱 |
gen | 生成下一條指令,cx加1 |
<while語句> ::= <while><條件><do><語句>
case WHILESYM: __________; // 第一個空爲cx1 = cx,記錄JMP要跳轉的位置 getsym(); condition(SymSetAdd(DOSYM, FSYS), LEV, TX); __________; // 第二個空爲cx2 = cx,記錄JPC指令的位置 gen(JPC, 0, 0); if (__________) // 第三個空爲sym == DOSYM,處理到do getsym(); else error(18); statement(fsys, lev, tx); gen(__________); // 第四個空爲jmp, 0, cx1,要知道跳轉回開始判斷條件的地方 __________; // 第五個空爲code[cx2].a = cx,回填JPC指令的跳轉位置 break;
if條件語句圖
if-else條件語句圖
<條件語句> ::= <if><條件><then><語句>[<else><語句>]
下題處理了if條件語句和if-else條件語句的狀況:
case IFSYM: getsym(); condition(SymSetUnion(SymSetNew(THENSYM, DOSYM), FSYS), LEV, TX); if (SYM == THENSYM) getsym(); else error(16); ________; // 第一個空爲cx1 = cx;,記錄JPC位置待回填 gen(JPC, 0, 0); statement(SymSetUnion(SymSetNew(ELSESYM), FSYS), LEV, TX); if (__________) // 第二個空爲SYM != ELSESYM,此時在分析else符號 code[cx1].a = cx; else { getsym(); cx2 = cx; gen(JMP, 0, 0); __________; // 第三個空爲code[cx1].a = cx,此時在分析false部分的語句開頭,回填JPC的地址 statement(FSYS, LEV, TX); __________; // 第四個空爲code[cx2].a = cx,此時爲執行完true部分語句後的JMP回填跳轉地址 } break;