National Central University
Uedu Main Site
Explore Uedu
Student Console
Register as Member/Login
Research Informed Consent Center
Survey Center
Educational Big Data Challenge
Claim or create a course
Try an AI Scenario Task
Support & Messages
Uptime Data

UeduGPTs

--

Jupyters

3

Local AI

--

Uedu Code

--

中央大學 AQI 80 24°C PM2.5 21
AI Reply Desktop Notifications

Show a desktop notification when the AI TA finishes replying

Chat Message Notifications

Notify me when classmates post messages in the forum

Sound notification

Play an alert sound whenever there is a new notification

Unit 20

程式語言演進:編譯與直譯

機器語言、組合語言到高階語言
Interaction 25 minute(s)

Learning Objectives

  • 區分機器語言、組合語言與高階語言,說明組譯器、編譯器、直譯器各自的角色
  • 認識幾個重要高階語言出現的年代與用途(FORTRAN、COBOL、BASIC、C、Python、Java、JavaScript)
  • 說明編譯器的主要階段:詞法分析、語法分析、語意檢查、產生程式碼
  • 比較編譯與直譯在執行速度、錯誤發現時機與可攜性上的差異,認識位元組碼、虛擬機器與即時編譯

這一課要回答的問題

第 10 課說過,CPU 只看得懂一串一串的 0 與 1:機器指令。可是你寫的是:

total = price * count

你可能也聽過這些說法:

  • 「Python 比 C 慢」
  • 「這個軟體只有 Windows 版,Mac 不能用」
  • 「Java 寫一次,到處都能執行」

這一課要回答:人寫的程式是怎麼變成 CPU 能執行的指令?「編譯」和「直譯」差在哪裡?

機器語言、組合語言、高階語言

同一件事「total = a + b」,在三個層次長這樣(指令是本課模擬器自訂的教學用指令集):

層次 寫法 誰看得懂
機器語言(machine language) 20 00 00 20 00 01 30 21 00 02(十六進位) CPU 直接執行;每種 CPU 架構的指令編碼都不同
組合語言(assembly language) LOAD a LOAD b ADD STORE total 用好記的英文縮寫代替數字,由組譯器(assembler)一對一翻成機器碼;仍然綁定某一種 CPU
高階語言(high-level language) total = a + b 接近人的思考方式,不必管暫存器與記憶體位址;一行可能變成好幾條機器指令,由編譯器或直譯器處理

早期的程式設計師直接寫機器碼或組合語言,程式又長又難改,換一台不同的電腦就要重寫。高階語言讓人專心在「要做什麼」, 把「怎麼用這顆 CPU 做到」交給翻譯程式。

幾個重要的高階語言

年代 語言 起源與用途
1957 FORTRAN IBM 的 John Backus 團隊開發,第一個廣泛使用的高階語言,用於科學與工程計算
1959 COBOL 由業界與政府組成的委員會(CODASYL)制定,用於商業資料處理;至今仍有銀行與政府系統在使用
1964 BASIC 美國達特茅斯學院(Dartmouth College)開發,讓非理工科的學生也能寫程式
1972 C 貝爾實驗室的 Dennis Ritchie 為了開發 Unix 作業系統而設計;許多作業系統與後來的語言都受它影響
1991 Python Guido van Rossum 在荷蘭的研究機構 CWI 開發,語法簡潔易讀
1995 Java 昇陽電腦(Sun Microsystems)發表,編譯成位元組碼在虛擬機器上執行
1995 JavaScript Brendan Eich 在網景(Netscape)開發,讓網頁可以在瀏覽器裡執行程式

語言還可以依程式設計典範(paradigm)分類:一步一步下指令的程序式(C)、以物件組織程式的物件導向(Java)、 以函數組合為主的函數式(Haskell),以及只描述「要什麼結果」的宣告式(SQL)。很多語言(Python、JavaScript)同時支援好幾種寫法。

編譯器怎麼翻譯一支程式

編譯器(compiler)在執行之前,把整支原始碼一次翻譯成目標程式(機器碼或其他指令),主要經過這幾個階段:

階段 做什麼 例子
詞法分析(lexical analysis) 把一長串字元切成有意義的最小單位:token(關鍵字、名稱、數字、運算子) while i <= n → while、i、<=、n
語法分析(parsing) 依照語言的文法,把 token 組成語法樹(syntax tree),運算的先後順序就在樹的形狀裡 a + b * 2 的樹,* 在下層先算
語意檢查(semantic analysis) 檢查語法正確但意思有問題的地方:變數沒有宣告、型別不合 把字串和數字相乘
產生程式碼(code generation) 把語法樹轉成目標指令,常再經過最佳化(optimization)讓它更快、更小 while 變成比較指令+條件跳躍+跳回去

直譯器(interpreter)則不先翻成一整支目標程式,而是讀一段、分析一段、馬上執行一段。 下面的模擬器用的是本課自己設計的小語言,你可以按每個階段的按鈕,看同一支程式一步一步變成指令;也可以改用直譯器執行,比較兩種做法。

互動練習:從原始碼到指令

左邊是一支計算 1 加到 n 的小程式。依序按 ① ② ③ ④ 四個階段的按鈕,看它被切成 token、建成語法樹、變成堆疊機器指令與機器碼,最後執行;每完成一步,下方會出一題小問題。接著按上方的「用直譯器執行」,再用「執行次數」滑桿比較「編譯一次、執行多次」與「每次直譯」的工作量;最後把第 2 行改成 n = 50,兩種方式都再執行一次。工作量是示意單位,不是實際時間。

逐步執行:堆疊機器怎麼執行指令

剛剛模擬器產生的指令,是給一台「堆疊機器」執行的。這個 Python 範例就是一台極小的堆疊機器(也就是一個直譯器):它不斷「取一條指令、看是什麼、照著做」。看上方的 stack 怎麼長高又變矮:PUSH 放一個數上去,MUL、ADD 取出兩個數算完再放回去。

想一想:語言的層次與堆疊機器

1.

關於組合語言,下面哪一個敘述正確?

2.

把上面堆疊機器的指令換成下面這兩行,執行後會印出什麼?

ops = ['PUSH', 'PUSH', 'ADD', 'PUSH', 'MUL', 'PRINT']
args = [2, 3, 0, 4, 0, 0]
3.

原始碼 a + b * 2 經過語法分析後,語法樹最上層(根)的運算是?

第 1 次作答

編譯、直譯,以及兩者之間

編譯(compile) 直譯(interpret)
什麼時候翻譯 執行之前,整支程式一次翻完 執行的同時,一段一段處理
執行速度 通常比較快:翻譯與最佳化只做一次 通常比較慢:執行時還要花時間分析
錯誤什麼時候發現 語法、型別等錯誤在編譯時就發現,程式根本不會開始跑 執行到那一行才發現;沒執行到的錯誤可能一直藏著
修改後 要重新編譯 改完直接執行,適合邊試邊改
可攜性 編出來的執行檔綁定某種 CPU 與作業系統(所以有 Windows 版、Mac 版) 有直譯器的地方就能執行同一份原始碼
例子 C、C++、Go、Rust 早期的 BASIC、殼層腳本(shell script)

現代的語言大多介於兩者之間:

  • 位元組碼+虛擬機器:Java 先編譯成和 CPU 無關的位元組碼(bytecode),由各平台的 Java 虛擬機器(JVM)執行,這就是「寫一次,到處執行」。 Python(官方實作 CPython)也會先把原始碼編譯成位元組碼(你可能看過 .pyc 檔),再由虛擬機器一條一條執行,所以常被歸類為直譯式語言。
  • 即時編譯(just-in-time,JIT):執行時發現某段程式跑得很頻繁,就把它即時編譯成機器碼。Java 虛擬機器與瀏覽器的 JavaScript 引擎都用這種技術,兼顧可攜性與速度。

所以「Python 比 C 慢」的主要原因之一,是 Python 程式執行時還要經過虛擬機器一條一條處理位元組碼; 同一個演算法,用 C 編譯成機器碼直接在 CPU 上跑,通常快很多。但 Python 寫起來快、改起來快,很多時候開發時間比執行時間更重要。

下面的模擬器準備了四支各有一個錯誤的程式。猜猜每個錯誤會在哪一個階段被發現,再實際編譯驗證。

互動練習:錯誤在哪一個階段被發現

用上方的選單切換程式 A~D,每一支都按「一次編譯到底」,看編譯器在哪一個階段停下來(原始碼的行號會把錯誤的那一行標成紅色),再回答下方的問題。最後用「用直譯器執行」跑程式 C,比較直譯器的反應。

想一想:編譯與直譯

1.

程式裡有一行 x = (3 + 4(少了右括號)。編譯器會在哪一個階段發現?

2.

你在 Windows 上用 C 編譯出 game.exe。把這個檔案複製到一台 Mac,為什麼不能直接執行?

3.

關於 Java 的「寫一次,到處執行」,下面哪一個說明正確?

第 1 次作答

重點整理

  • 機器語言是 CPU 直接執行的二進位指令;組合語言用縮寫代表機器指令,由組譯器一對一翻譯;高階語言接近人的思考,由編譯器或直譯器處理。
  • 重要的高階語言:FORTRAN(1957,科學計算)、COBOL(1959,商業)、BASIC(1964,教學)、C(1972,Unix)、Python(1991)、Java 與 JavaScript(1995)。
  • 編譯器的階段:詞法分析(切成 token)→ 語法分析(建語法樹)→ 語意檢查 → 產生程式碼(常加上最佳化)。
  • 迴圈與 if 在指令層次都是「比較+條件跳躍+跳躍」;堆疊機器靠堆疊存放計算的中間結果。
  • 編譯:先翻完再執行,通常比較快、錯誤提早發現,但執行檔綁定平台;直譯:邊分析邊執行,改了就能跑、可攜,但比較慢,沒執行到的錯誤會藏著。
  • Java、Python 先編譯成位元組碼再由虛擬機器執行;即時編譯(JIT)在執行時把常用的部分編成機器碼。

想實際寫程式,可以接著上 Python 軌;想看一行程式在 CPU 裡怎麼執行,回頭複習第 10、11 課。

下一課:開始寫程式。變數、型別與控制流程是所有程式語言共同的基本元素。

這一課完成了!
Next lesson:變數、型別與控制流程
你目前沒有登入,學習進度只存在這個瀏覽器。 Login 後,進度會跟著帳號,換電腦也看得到。