組合語言程式設計:與 CPU 直接對話

歡迎來到組合語言(Assembly Language)的世界!別擔心,這個詞聽起來可能有點嚇人,但其實它只是連接你在 Python 或 Java 等高階程式語言,與中央處理器(CPU)實際執行的原始二進位指令(機器碼)之間的橋樑。

理解組合語言程式設計非常重要,因為它能讓你清楚看見電腦在最底層是如何運作的——包括資料如何移動、計算如何發生,以及控制流程如何在程式中流動。這絕對是深入了解電腦運作核心的最佳視角!

導論重點筆記:

組合語言是一種低階程式語言,它使用簡單的文字指令(助憶碼,mnemonics)來代表機器碼指令。


1. 機器碼與組合語言:底層視角

程式語言是根據它們與硬體的「接近程度」來分類的。

低階語言(靠近硬體)

這些語言需要對特定的電腦架構(如 CPU 設計和暫存器)有深入的了解。其中包括:

1. 機器碼(Machine Code):

  • 這是 CPU 本身的語言。
  • 完全以二進位(0 與 1)表示。
  • 每條指令都對應特定的處理器類型(具有處理器依賴性)。
  • 對人類來說,閱讀和編寫都極為困難。

2. 組合語言(Assembly Language):

  • 這是機器碼的符號化表示。
  • 它使用簡短、易記的代碼,稱為助憶碼(例如 ADD, LOAD, SUB),而不是二進位數字。
  • 通常存在一對一關係:一條組合語言指令會轉譯成一條機器碼指令。
  • 它同樣具有處理器依賴性,這意味著為某種 CPU(如 ARM)編寫的組合語言程式,若不經修改,無法在另一種 CPU(如 x86)上執行。

你知道嗎?「助憶碼」(mnemonic)一詞源自希臘語,意指「記憶相關」,透過這些指令幫助你記住其功能(例如 ADD 代表加法)。

低階語言與高階語言的比較

你可能會好奇,既然 Python 這種語言這麼容易上手,為什麼還要使用組合語言呢?

組合語言的優勢:

  • 速度/效率:由於程式設計師能直接控制硬體,可以優化資源使用(時間效率與記憶體/空間效率),因此程式執行速度快得多。
  • 硬體存取:允許直接操作暫存器和記憶體,這對於編寫作業系統組件或裝置驅動程式至關重要。
  • 大小:產生的程式檔案非常小。

組合語言的劣勢:

  • 困難度:相比高階語言,編寫和除錯的難度更高、耗時更長。
  • 可攜性:程式碼不具可攜性;若更換不同的處理器類型,程式必須重寫。
  • 可維護性:維護成本高,通常只有原始程式設計師才能完全理解其邏輯。
快速複習:轉譯器的角色

低階程式在 CPU 執行前,需要一個轉譯器:

組合語言(助憶碼) \(\rightarrow\) 組譯器 (Assembler) \(\rightarrow\) 機器碼(二進位)


2. 指令格式:藍圖結構

無論是二進位機器碼還是組合語言助憶碼,每條指令都遵循一套標準藍圖。

處理器的整個指令詞彙表稱為處理器指令集(processor instruction set)。該指令集中的所有指令通常由三個主要部分組成:

1. 操作碼(Opcode, Operation Code)

這是指令中指定「要執行什麼操作」的部分。

  • 範例: ADD, SUB, LOAD, JMP (跳躍)。
  • 類比: 句子中的動詞(例如「加」)。
2. 定址模式(Addressing Mode)

這指定了「如何詮釋運算元(資料)」——它是數值本身,還是記憶體位址?

  • 我們將在第 3 節詳細探討這個棘手的概念!
3. 運算元(Operands)

這是指令要運作的對象(資料、記憶體位址或暫存器)。

  • 範例: 如果指令是 "ADD 5, R1",那麼運算元就是 5 和暫存器 R1。
  • 類比: 句子中的名詞或受詞(例如「5」和「暫存器一」)。
範例:典型的 32 位元指令格式

雖然你不必背誦特定的格式,但要知道指令通常會劃分為多個位元組。例如,前 4 個位元可能是操作碼,接下來的 1 個位元是定址模式,剩下的 27 個位元則是運算元。


3. 定址模式:尋找資料

定址模式告訴 CPU 在哪裡找到操作所需的資料。釐清這三種類型非常重要!

1. 立即定址(Immediate Addressing, IMM)

在立即定址中,運算元本身就是指令中要使用的實際數值

  • 數值可以直接取得(立即)。
  • 助憶碼範例: LOAD R1, #5(將數字 5 載入到暫存器 R1)。
  • 類比: 直接把東西交給某人:「拿去這 5 英鎊。」
2. 直接定址(Direct Addressing, DIR)

在直接定址中,運算元就是儲存資料的記憶體位址

  • CPU 必須前往該位址以提取數值。
  • 助憶碼範例: ADD R1, 100(將儲存於記憶體位址 100 的值加到 R1)。
  • 類比: 給某人一個位置:「去銀行 100 號保險箱,拿出裡面的東西。」

注意: 在某些語境下,直接定址可能指使用暫存器編號而非主記憶體位址。

3. 間接定址(Indirect Addressing, IND)

這是最棘手但威力最強大的一種!在間接定址中,運算元(通常是暫存器或記憶體位置)存放的是實際資料所在的位址

  • CPU 會讀取該運算元以找到位址,然後再前往那個位址去提取數值(兩步查詢)。
  • 助憶碼範例: LOAD R1, (R2)(將暫存器 R2 中目前存放的記憶體位址裡的值,載入到 R1)。
  • 類比: 給某人一把儲物櫃鑰匙,儲物櫃裡寫著寶藏箱的位址:「去 R2 號儲物櫃,讀取裡面寫的位址(例如 500),然後再去 500 號地址尋找真正的寶藏。」
常見錯誤警示!

學生經常搞混直接定址與間接定址:

  • 直接定址:運算元 = 數值的位址。
  • 間接定址:運算元 = 存放了數值位址的位址。

4. 基本組合語言操作 (3.8.2)

組合語言指令可根據其功能分組。在編寫或追蹤程式時,你必須理解並能應用這些基本操作。

4.1 資料傳輸操作
  • LOAD:將資料從記憶體(或立即數值)移動到 CPU 的暫存器中。
  • STORE:將資料從暫存器移動到主記憶體的特定位置。

範例: LOAD R1, #10(立即載入)
範例: STORE R1, 200(將 R1 的內容儲存到記憶體位址 200)

4.2 算術操作

執行數學計算,通常涉及儲存在暫存器中的數值。

  • ADD:將兩個數值(運算元)相加,並將結果放置在指定位置(通常是暫存器)。
  • SUB:將兩個數值相減。
4.3 控制流程操作(分支與比較)

這些指令會改變指令執行的順序,從而實現決策、迴圈和副程式。

  • COMPARE (CMP):比較兩個數值。這不會改變暫存器的值,但會設定狀態暫存器(Status Register, SR)中的旗標。
  • BRANCH (條件式/非條件式):改變程式計數器(Program Counter, PC)的值,以跳躍到新的指令位址。
    • 非條件式分支(如 JMP, B):無論比較結果為何都執行跳躍。
    • 條件式分支(如 JNE, BEQ):僅在符合特定條件(由先前的 COMPARE 指令設定)時才跳躍(例如:若不相等則跳躍、若相等則分支)。
  • HALT:停止程式執行。
4.4 邏輯與位元運算

這些指令對位元組或字組內的個別位元進行布林運算。對於遮罩(masking)、設定或檢查特定旗標/位元至關重要。

  • 邏輯位元運算子: AND, OR, NOT, XOR(互斥或)。
  • 範例: 位元 AND 可用於檢查狀態暫存器中的特定位元是否設為 1。
4.5 位移操作

將暫存器中的所有位元向左或向右移動。這是進行 2 的冪次乘除法極為高效的方法。

  • SHIFT RIGHT (右移):將所有位元向右移動。這等同於進行整數除以 2
  • SHIFT LEFT (左移):將所有位元向左移動。這等同於進行乘以 2

注意: 位移時,從末端掉出的位元通常會被丟棄,並在空出的位置補上零(例如右移時補在最左側)。

操作重點筆記:

組合語言操作簡單且原子化(每次只做一件小事)。複雜的程式是由成千上萬條這類簡單指令依序組合而成的。


5. 追蹤與轉換組合語言程式

在考試中,你需要展示你能夠透過追蹤(tracing)組合語言程式來理解邏輯,並能根據需求或虛擬碼編寫簡單的組合語言程式。

暫存器在追蹤中的角色

追蹤組合語言程式碼時,必須隨時留意 CPU 關鍵元件的內容,特別是通用暫存器和記憶體位址。指令通常會修改這些位置的內容。

  • 類比: 暫存器就像 CPU 上超快的小型塗鴉板,用於即時計算。
虛擬碼轉組合語言(及反向轉換)

你必須能夠將用虛擬碼(pseudocode)表達的演算法轉換為組合語言,並能將組合語言解讀回虛擬碼。

轉換情境範例(虛擬碼轉組合語言):

虛擬碼:
IF NumberA > NumberB THEN Output = NumberA

簡化的組合語言邏輯:

  1. LOAD R1, NumberA(直接定址 NumberA)
  2. LOAD R2, NumberB(直接定址 NumberB)
  3. COMPARE R1, R2
  4. BRANCH_LE END_IF(若小於或等於則分支至 END_IF)
  5. STORE R1, Output(將 NumberA 的值儲存至 Output 位址)
  6. END_IF: HALT

鼓勵: 轉換迴圈與選擇結構時,需要小心使用 COMPAREBRANCH 指令來控制程式計數器(PC)的流向。


6. 程式轉譯:組譯器 (3.6.3.2)

為了執行組合語言,我們需要一個稱為組譯器(assembler)的轉譯器。

什麼是組譯器?

組譯器是一種系統軟體,其唯一目的就是將組合語言(使用助憶碼編寫的原始碼)轉譯成機器碼(物件碼/可執行檔)。

原始碼 vs. 物件碼
  • 原始碼 (Source Code):人類可讀的程式碼(例如組合語言助憶碼:ADD R1, #5)。
  • 物件碼/可執行碼 (Object/Executable Code):轉譯器輸出的二進位碼,CPU 可直接執行(例如:01001001 00000101)。

由於組合語言與機器碼之間存在直接的一對一映射關係,組譯器執行的轉譯過程通常比高階語言所需的編譯或解譯更簡單、更快速。

章節總結:組合語言核心概念

1. 組合語言是一種低階語言,使用助憶碼,需透過組譯器進行轉譯。

2. 指令由操作碼 (Opcode)定址模式 (Addressing Mode)運算元 (Operands) 組成。

3. 三種定址模式為:立即定址(運算元即數值)、直接定址(運算元即位址)、間接定址(暫存器存放資料位址)。

4. 核心操作包括資料移動 (LOAD/STORE)、算術 (ADD/SUB)、流程控制 (COMPARE/BRANCH) 及位元操作 (邏輯/位移)。