You are on page 1of 53

系統程式 – 理論與實務

第 7 章、高階語言

作者:陳鍾誠

旗標出版社
第 7 章、高階語言
7.1 簡介
7.2 語法理論
7.3 語義理論
7.4 執行環境
7.5 實務案例
7.5.1 C 語言與 GNU 工具
7.1 簡介
高階語言的設計一直是程式設計人員關注的焦點,

從 1960 年代以來,人們不斷發明新的程式語言
高階語言就好像流行服飾一般,不斷的推陳出新,

這使得目前已知的程式語言達數百種之多,沒有任
何人能熟悉所有的高階程式語言。
圖 7.1 高階語言的歷史年表
1954 Fortran
1956
1958 Lisp
Cobo Algol
1960
1962 l
1964 PL/I
1966
1968
Smalltalk Eiffel ML Prolog
1970 Pascal
1972 C
1974 Scheme
1976
1978
1980 Smalltalk 80
1982 C++ Common SML
1984 Ada 83 Lisp
1986 Eiffel Perl Caml
1988 Tcl Haske
Python
1990 Java ll
1992
1994 Ruby
1996
OCaml
1998
2000 C#
2002
2004
2006 F#
高階語言的核心 – 語法理論
語法理論
我們可以利用生成規則 ( 例如: BNF, EBNF 等 )
描述程式的語法。
一但能正確的描述某個程式語言,就能撰寫剖析該語
言的剖析程式,將這些語法轉換成語法樹 ( 或稱剖
析樹 ) 。
高階語言的解讀 – 語義理論
一但語法樹建構完成,就可以進行『解譯』或『目
的碼產生』的動作。
如果我們撰寫程式以解讀該語法樹,並根據節點類
型值型對應的動作,這樣的程式就被稱為『直譯
器』。
如果我們撰寫程式解讀語法樹時,會根據節點類型
產生低階的可執行碼 ( 例如機器碼 ) ,那麼,這樣
的程式就被稱為編譯器。
7.2 語法理論
生成語法

 近代語言學之父的喬姆斯基 (Chomsky) 所發明

 分為 0, 1, 2, 3 型

第 0 型描述能力最強,第 3 型最弱
表格 7.1 喬姆斯基語法階層
文法 語言 自動機 產生式 規則
0- 型 遞迴可枚舉語言 圖靈機
(Recursively Enumerable) (Turing Machine) α-> β

1- 型 上下文相關語言 線性有界非確定圖靈 αAβ ->


(Context Sensitive 機 αγβ
Grammar) (Linear Bounded
Automata)
2- 型 上下文無關語言 非確定下推自動機 A -> γ
(Context Free Grammar) (Push Down Automata)

3- 型 正規語言 有限狀態自動機 A -> aB


(Regular Grammar) (Finite State Automata) A -> a
高階語言所使用的語法
高階語言所使用的語法,大致上分為兩個層次

單詞的語法

第 3 型:使用『正規語法』 (Regular Grammar)

句法結構

第 2 型:使用『與上下文無關的文法 』 (Context-

Free Grammar ,簡稱 CFG)


圖 7.2 簡單的生成語法範例

S  AB. L = {ac, ad, bc, bd}


A  "a" | "b".
B  "c" | "d".
圖 7.3 一個簡單的英語語法範例

S N V
N  “John” | “Mary”
V  “eats” | “talks”
具有歧義的語法

E  N | E "+" E | E "-" E.

N  "1" | "2" | "3" | "4" .

圖 7.4 簡單但有歧義的數學運算式描述語法
歧義性的範例

E E

E “-” E E “-” E

E “-” E N N E “-” E

N N “2” “3” N N
“1 “2
“3” “1”
” ”

(a). 語義: (3 - 1) - 2 = 0 (b). 語義: 3 - (1 - 2) = 4

圖 7.5 運算式 3-1-2 可能產生兩顆語義不同的語法樹


圖 7.6 數學運算式的語法

E  T | E "+" T | E "-" T .

T  F | T "*" F | T "/" F.

F  V | "(" E ")".

V  "1" | "2" | "3" | "4".


圖 7.7 數學運算式 (1+2*3) 的語法樹
範例

E “+” T

T T “*” F

F F V
“3
V V

“1” “2”
BNF 語法
 Chomsky 生成語法中的 Context-Free Grammar 與 Backus 和 Naur

兩人所提出的 BNF 語法,可以說是同一件事。


 然而, BNF 針對程式語言進行精確的描述,在描述語法上更為明確,可

以說是 CFG 語法的一個具體描述方式。


 在許多文章當中,早已將 CFG 與 BNF 兩者的意義視為相同,這是沒

有太大影響的。
 為了明確起見,在本書當中,我們用 BNF 來指稱『以電腦字元集的固

定格式,表示 CFG 的一種特別語法』,例如,我們可以將圖 7.6 當中


的 CFG 寫成如圖 7.8 右側的 BNF 語法。這樣的改寫有助於將 BNF
語法以電腦鍵盤打出,而不需要考慮到特殊符號的表達問題。
圖 7.8 將 < 圖 7.6> 的 CFG 改
寫為 BNF 語法
Context Free Grammar BNF 語法

E  T | E "+" T | E "-" T . <E> ::= <T> | <E> "+" <T> |


T  F | T "*" F | T "/" F. <E> "-" <T>
F  V | "(" E ")". <T>::=<F>|<T> "*" <F> | <T>
V  "1" | "2" | "3" | "4". "/" <F>
<F>::=<V> | "(" <E> ")"
<V>::= "1" | "2" | "3" | "4"
EBNF 語法
在編譯器的設計上, BNF 當中的左遞迴,是相當

難以處理的
Pascal 語言的發明人 Nicklaus Wirth 發明了一種

BNF 的延伸語法 EBNF , EBNF 可以用來消除


大部分的左遞迴
加入大括號 {} ,用以表示多次出現的意思

用中括號將可能的終端符號利用 | 直接連接起來
圖 7.9 將 < 圖 7.8 > 的 BNF 語
法改寫為 EBNF 語法
BNF 語法 EBNF 語法
<E> ::= <T> | <E> "+" <T> | <E> ::= <T> { ["+"|"-"]
<E> "-" <T> <T> }
<T>::=<F>|<T> "*" <F> | <T> <T>::=<F> { ["*"|"/"]
"/" <F> <F> }
<F>::=<V> | "(" <E> ")" <F>::=<V> | "(" <E> ")"
<V>::= "1" | "2" | "3" | "4" <V>::= "1" | "2" | "3" | "4"
圖 7.10 加入詞彙規則後的數學運算式之 EBNF

EBNF 語法規則 Regular Expression 詞彙辨識


規則
<E> ::= <T> <id>::=[A-Za-z_][ A-Za-z0-
{ ["+"|"-"] <T> } 9_]*
<T>::=<F> { ["*"|"/"] <int> ::= \-? [0-9]+
<F> }
<F>::=<V> | "(" <E>
")"
<V>::=<id> | <int>
7.3 語義理論
語法理論說明了程式語言語法的描述方式

語義理論說明了語法所對應的意義,也就是程式應

該如何執行的規範。透過語義理論,程式才能被賦
予明確的意義,並且確定每個語句的執行方式。
程式語言的語義
結構化的語義

物件導向語義

邏輯式語義
Prolog 採用邏輯式語義,其語法構造與結構化語法
大為不同。
Prolog 有三種基本的句型 1. 事實 (Facts) 2. 推論
(clause) 3. 問題 (query) ,這三個基本句型又都以
『謂詞』 (predicate) 語法為基礎。
範例 7.1 快速排序法的 C 語言程

void quicksort(double a[], int left, int right) {
int last = left, i;
if (left >= right) return;
swap(a,left,Random(left,right));
for (i = left + 1; i <= right; i++)
if (a[i] < a[left])
swap(a,++last,i);
swap(a,left,last);
quicksort(a,left,last-1);
quicksort(a,last+1,right);
}
void swap(double a[], int i, int j) {
double tmp = a[i];
a[i] = a[j];
a[j] = tmp;
}
範例 7.2 快速排序法的 Prolog
程式
quicksort([X|Xs],Ys) :-
partition(Xs,X,Left,Right),
quicksort(Left,Ls),
quicksort(Right,Rs),
append(Ls,[X|Rs],Ys).
quicksort([],[]).
partition([X|Xs],Y,[X|Ls],Rs) :-
X <= Y, partition(Xs,Y,Ls,Rs).
partition([X|Xs],Y,Ls,[X|Rs]) :-
X > Y, partition(Xs,Y,Ls,Rs).
partition([],Y,[],[]).
append([],Ys,Ys).
append([X|Xs],Ys,[X|Zs]) :- append(Xs,Ys,Zs).
結構化語義
大部分的程式語言採用結構化語義

包含『指定、運算、循序、分支、迴圈、函數』等
結構
表格 7.2 結構化程式的構造方式
結構類型 語法 範例
指定結構 <ASSIGN> ::= <VAR> = x = 3*y+5
<EXP>
運算結構 <EXP> ::= <T> 3*y+5
{ ["+"|"-"] <T> }
循序結構 <BASE_LIST > ::= t=a; a=b; b=t;
{ <BASE> }
分支結構 <IF> ::= if (<COND>) if (a>b) c=a; else c=b;
<BASE>
{ else if (<COND>)
迴圈結構 <BASE> }::=
<WHILE> [else <BASE> while (i<=10) { sum =
]while (<COND>) sum+i; i++;}
<BASE>
函數結構 <FUNC_DEF> ::= 定義: max(a,b) {if (a>b)
<FUNC>({<ARGS>}) return a;
<BLOCK> else return b;}
<FUNC_CALL>::= 呼叫: c = max(3,5);

<FUNC>({<PARAMS>});
物件導向語義
第一個物件導向語言是 SmallTalk

SmallTalk 80 的物件導向概念成為後來許多語言競

相模仿的對象
像是 C++, Java, C# 等都是將 C 語言擴充以支持

物件導向與義的結果
物件導向語意主要是將資料與函數 在一起,形成類
別與物件結構。
物件的定義 – 類別
 類別的語法
 class <CLASS_NAME> { <VAR_DEF>* <FUNC_DEF>* }
 範例: class Stack { String array[], push(o) {…} , pop {…} }

 物件的建立
 new <CLASS_NAME>()
 範例: s = new Stack()

 成員的存取
 存取物件變數: <CLASS_NAME>.<VAR>
 範例: s.array
 呼叫物件函數: <CLASS_NAME>.<FUNC>
 範例: s.push(“hello”)
邏輯式語義
Prolog 採用邏輯式語義,其語法構造與結構化語
法大為不同。

Prolog 有三種基本的句型
1. 事實 (Facts)
2. 推論 (clause)
3. 問題 (query)

這三個基本句型又都以『謂詞』 (predicate) 語法為


基礎。
邏輯語句中的謂詞
 一個謂詞 <PR> 的語法可用 <P>(<ARGS>) 表示

這個語法有點像結構化中的函數,但其傳回 永遠為
布林值,只有真與假兩種。
 例如
 Mother(Mary, Peter)

 Ancestor(a,c)

 Ancestor(a,b)

 Ancestor(b,c)

 Ancestor(x,Peter)
表格 7.3 程式語言 Prolog 的構造方

結構類型 BNF 語法 範例

謂詞結構 <PR> ::= <P>(<ARGS>) Mother(Mary, Peter)

事實結構 <FACT> ::= <PR>. Mother(Mary, Peter).

推論結構 <CLAUSE> ::= <PR> :- Ancestor(a, c) :-


<PR>{, <PR>}. Ancestor(a,
b),Ancestor(b,c).

問題結構 <QUERY> ::= ?- <PR>. ?- Ancestor(x,Peter).


範例 7.3 Prolog 的程式範例與
回答
Prolog 推論 說明
Ancestor(a, c) :- Ancestor(a, b), 若 a 是 b 的祖先,且 b 是 c 的
Ancestor(b,c). 祖先,
Ancestor(a, b) :- Parent(a,b). 則 a 是 c 的祖先
Parent(a,b) :- Mother(a,b). 若 a 是 b 的尊親,則 a 是 b 的
Parent(a,b) :- Father(a,b). 祖先
若 a 是 b 的母親,則 a 是 b 的
Prolog 事實 說明
尊親
Mother(Mary, Peter). 若 a 是是
Mary b 的父親,則 a是b的
Peter 的母親
Father(Bob, Mary). 尊親
Bob 是 Mary 的父親
Prolog 問題 說明
?- Ancestor(x,Peter). 誰是 Peter 的組先 ?
Prolog 的回答 說明
Ancestor(Mary, Peter). Mary 是 Peter 的祖先
Ancestor(Bob, Peter). Bob 是 Peter 的祖先
7.4 執行環境
直譯
直接執行剖析後的語法樹,

編譯
將語法樹轉換成機器碼後,再由載入器放入記憶體執
行。
直譯
利用直譯器建構出一個模仿語義理論的環境

利用直譯器的動作,模擬出對應的操作語義。
表格 7.4 結構化程式的直譯過程
結構類型 語法 直譯器動作
指定結構 <ASSIGN> ::= <VAR> = 將 <EXP> 算出後,放入符號表
<EXP> 的 <VAR> 變數中
運算結構 <EXP> ::= <T> 將 <T> + <T> 的結果,放入
{ ["+"|"-"] <T> } <EXP> 節點中。
循序結構 <BASE_LIST> ::= 循序執行 <BASE_LIST> 的子
{ <BASE> } 節點, <BASE1> <BASE2>
分支結構 <IF> ::= if (<COND>) … <BASEK>
檢查條件 <COND> 節點的值,
<BASE> 如果為真,則執行對應的
{ else if (<COND>) <BASE> ,若均為假,則執行
<BASE> } [else <BASE> ] else 語句中的 <BASE>
迴圈結構 <WHILE> ::= 當 <COND> 節點的值為真時,
while (<COND>) <BASE> 執行 <BASE> 節點,直到
<COND> 節點的值為假時,才
跳到下一個語句中。
函數結構 <FUNC_DEF> ::= 當呼叫函數 <FUNC_CALL> 時
<FUNC>({<ARGS>}) ,將 <ARGS> 參數取代為
<BLOCK> <PARAMS> ,然後執行
<FUNC_CALL>::= <BLOCK> 區塊
直譯器的演算法 (1) -
<ASSIGN>
直譯器的演算法 說明
Algorithm run(node) 解譯 node 節點 ( 以遞迴方式
switch (node.tag) { )
… 判斷節點類型
case ASSIGN
var = node.childs[0] 如果是指定敘述
exp = node.childs[2] <VAR>=<EXP>
SymbolTable[var] = run(exp) 取出變數
… 取出算式
End Algorithm 將算式的結果指定給變數
直譯器的演算法 (2) - <EXP>
解譯器的演算法 說明
case EXP 如果是算式 <T> +- <T>
term1 = node.childs[0] 取得第一個項目
run(term1) 解譯第一個項目
node.value = term1.value 設定父節點的值 ( 運算結果 )
for (i=1; i<node.childCount;
i+=2) 取得下一個運算符號
op = node.childs[i].tag 取得下一個運算元
term2 = node.childs[i+1] 解譯下一個運算元
run(term2) 如果是加號
if (op="+") 運算結果 += 運算元
node.value += 如果是減號
term2.value 運算結果 -= 運算元
else if (op="-")
node.value - term2.value
end if
end for
直譯器的演算法 (3) -
<BASE_LIST>
解譯器的演算法 說明
case BASE_LIST 如果是循序結構 { <BASE> }
for (i=1; i<node.childCount; 循序的執行每個子節點
i++)
run(node.childs[i])
end for
直譯器的演算法 (4) - <IF>
解譯器的演算法 說明
case IF if (<COND>) <BASE> elseif
for (i=1; i<node.chiidCount; …
i++) 查看每個子節點
if (node.childs[i].token = 如果是 if 關鍵字
"if") or 或者是 elseif 關鍵字
(node.childs[i].token = 取得條件節點
"elseif") 計算條件節點
cond = node.childs[i+2] 如果條件為真
run(cond) 取得 <BASE> 節點
if (cond.value = true) 執行 <BASE> 節點
base = node.childs[i+4]
run(base) 跳過 if (<EXP> )
end if <BASE>
i += 5 如果是 else 關鍵字
else if (node.childs[i].token 取得 <BASE> 節點
= "else") 執行 <BASE> 節點
直譯器的演算法 (5) - <WHILE>
解譯器的演算法 說明
case WHILE 迴圈: while (<EXP>) <BASE>
cond = node.childs[2] 取得 <COND> 節點
base = node.childs[4] 取得 <BASE> 節點
run(cond) 執行 <COND> 條件判斷
if (cond.value = true) 如果條件為真
run(base) 執行 <BASE> 節點
end if

while (<EXP>) <BASE>


直譯器的演算法 (6) -
<FUNC_CALL>
解譯器的演算法 說明
case FUNC_CALL 函數呼叫: FUNC>(<PARAMS>)
name = node.childs[0] 取得函數名稱
params = node.childs[2] 取得參數
取得函數內容
funcdef =
呼叫該函數
functionTable[name]
call(funcdef, params)
end switch
編譯
將語法剖析樹先轉換成機器語言,然後才執行。

一但編譯的動作完成之後,其執行速度通常很快

C 、 C++ 等語言通常就採用此種方式。

關於編譯器的設計方式,我們會在下一章中進行詳
細的介紹。
編譯器的輸出
編譯器編譯出來的目的碼,通常有兩類

第一種是真實電腦上的機器碼,像是 C/C++ 語言通

常就採用此種方式
第二種是虛擬機器上的中介碼,像是 Java, C# 就

採用此種虛擬的中介碼。
機器碼 v.s 虛擬碼
 機器碼
直接產生機器碼,其好處為執行速度非常快速,但是由於
真實電腦的結構差異很大,不同電腦間的機器碼結構通常
完全不相容,因此,這些機器碼不能跨越平台執行。
範例 : C, C++, …

 虛擬碼
使用虛擬碼可以讓目的碼跨越平台,因為,我們只要在不
同的電腦上撰寫一個虛擬機器,用來解譯這些目的碼即可
,因此,虛擬碼通常可以被傳遞到另一台電腦上執行,但
是,虛擬碼的執行速度較機器碼慢上一些,執行速度大約
慢兩倍到十倍都有可能。
範例 : Java, C#, …
編譯 v.s. 直譯
直譯式語言難道無法編譯成執行 ,直接執行 ?
這個問題的答案是,要看你對直接執行的定義是甚麼

?如果直接執行的方法,只不過是將一個直譯器直接
放入執行 當中,最後還是用那個直譯器對對程式進
行直譯的動作,那麼,當然所有的直譯式語言都可以
變成執行檔,因此,剩下的問題就變成了,到底執行
檔當中包含了多少解譯的動作在裡面呢?

範例 7.4 Python 中的 Lambda
表達式的使用
Python ( 直譯式 )
>>> foo = [2, 18, 9, 22, 17, 24, 8, 12, 27]
>>>
>>> print filter(lambda x: x % 3 == 0, foo)
[18, 9, 24, 12, 27]
>>>
>>> print map(lambda x: x * 2 + 10, foo)
[14, 46, 28, 54, 44, 58, 26, 34, 64]
>>>
>>> print reduce(lambda x, y: x + y, foo)
139
7.5 實務案例
7.5.1 C 語言與 GNU 工具
C 語言中的指標

char *ptr=(char*) 100;

*ptr = 'a'
函數指標
int (*compare)(const void *, const void *)
表格 7.5 glibc 函式庫的標頭檔分類表
(1)
ISO 標準 說明 POSIX 標準 說明 其他檔頭 說明
assert.h 錯誤偵測 cpio.h 壓縮格式 argz.h 參數取得
complex.h 數學複數 dirent.h 目錄操作 envz.h 參數取得
ctype.h 字元處理 fcntl.h 檔案操作 execinfo.h 除錯堆疊
errno.h 錯誤處理 grp.h 群組管理 fnmatch.h 字串比對
fenv.h 浮點環境 pwd.h 帳號密碼 gconv.h 國際轉換
float.h 浮點數 sys/ipc.h 行程通訊 langinfo.h 格式轉換
inttypes.h 整數轉換 sys/msg.h 訊息佇列 mcheck.h 記憶體檢查

iso646.h 運算詞彙 sys/sem.h 號誌 ulimit.h 資源限制


limits.h 數值範圍 sys/stat.h 檔案資訊 utmp.h 使用者帳號

locale.h 國際化 sys/time.h 日期時間 obstack.h 物件堆疊


math.h 數學函數 sys/types.h 型態定義 printf.h 參數剖析
setjmp.h 遠程跳躍 sys/utsname.h 平台型號 regex.h 正規表示式

signal.h 引發錯誤 sys/wait.h 行程等待 search.h 排序搜尋


表格 7.5 glibc 函式庫的標頭檔分類表
(2)
ISO 標準 說明 POSIX 標準 說明 其他檔頭 說明
stdarg.h 變動參數 tar.h 壓縮格式 sys/param.h 系統參數
stdbool.h 布林型態 termios.h 終端機 sys/resource.h 系統資源
stddef.h 通用定義 unistd.h UNIX sys/stat.h 系統設定
stdint.h 整數型態 utime.h 檔案時間 sys/time.h 系統時間
stdio.h 輸出入 sys/types.h 系統型態
stdlib.h 一般函數 sys/utsname.h 系統名稱
string.h 字串處理 sys/vlimit.h 系統限制
tgmath.h 數學函數 sys/vtimes.h 系統時間
time.h 日期時間 sys/wait.h 行程等待
wchar.h Unicode sys/socket.h 網路函數
wctype.h Unicode netdb.h 網路資料
netinet/in.h 網路輸入
sys/un.h 網址
第 7 章、高階語言 ( 本章摘要 )
7.1 簡介
C, C#, Java, Python, Prolog, ….
7.2 語法理論
生成語法、 BNF 、 EBNF
7.3 語義理論
結構化的語義:
 指定、運算、循序、分支、迴圈、函數
物件導向語義:
 類別的宣告、物件的建立、成員的存取
邏輯式語義:
 1. 事實 (Facts) 2. 推論 (clause) 3. 問題 (query)
 基礎:謂詞
第 7 章、高階語言 ( 本章摘要 )
7.4 執行環境
直譯 v.s. 解譯
機器碼 v.s. 虛擬碼

7.5 實務案例

7.5.1 C 語言與 GNU 工具


C 語言的指標
glibc 函式庫

You might also like