副程式與巨集:簡單明瞭的比較**
在微處理機的程式設計中,我們會發現有些程式碼會重複出現,這時我們可以使用 **副程式** 或 **巨集** 來簡化程式,讓它更容易閱讀和管理。那麼,這兩者有什麼不同呢?讓我們用簡單的方式來理解!
---
### **1. 副程式(Subroutine)—— 像是「工廠」**
**概念**:副程式是一段可以重複使用的程式碼,我們可以在主程式中「呼叫」它,然後執行完畢後回到主程式。
**比喻**:
- 想像你在家裡有一台「餅乾工廠」,每次想要餅乾時,你不需要自己動手做,只要按一下按鈕,工廠就會幫你做好並送來。
- 同樣地,當程式需要執行某個功能時,就可以「呼叫」副程式,副程式執行完後,再回到主程式繼續執行其他指令。
**特點**:
✔ 節省記憶體(只需要一份程式碼)
✔ 可以多次重複呼叫
✔ 執行時會「跳過去」,結束後「跳回來」
**例子(組合語言)**:
```assembly
CALL PRINT_MESSAGE ; 呼叫副程式
...
PRINT_MESSAGE:
MOV DX, OFFSET MESSAGE
CALL DISPLAY
RET ; 回到主程式
```
---
### **2. 巨集(Macro)—— 像是「食譜」**
**概念**:巨集是一種「事先寫好的指令組」,在程式編譯時,會直接展開並插入到程式中,就像是一份「固定食譜」,每次需要時都會重新複製一份來用。
**比喻**:
- 如果你有一份「餅乾食譜」,每次想要餅乾時,你都要自己按照食譜製作一次,每次做的餅乾都是獨立的,不會回去問工廠要。
- 在程式中,每次用到巨集,編譯器都會把這段程式碼複製到該處,而不是跳去執行它。
**特點**:
✔ 執行速度較快(不用跳來跳去)
✔ 但是會讓程式碼變長(每次都要複製一份)
✔ 不能像副程式那樣「回到主程式」,因為它只是單純展開指令
**例子(組合語言)**:
```assembly
MACRO PRINT_MESSAGE
MOV DX, OFFSET MESSAGE
CALL DISPLAY
ENDM
PRINT_MESSAGE ; 這裡會直接插入巨集的內容
```
---
### **3. 副程式 vs. 巨集:哪個比較好?**
| 比較項目 | 副程式(Subroutine) | 巨集(Macro) |
|---------|----------------|--------------|
| **程式碼大小** | 節省空間(只寫一次) | 會變大(每次複製) |
| **執行速度** | 可能較慢(需要跳轉) | 較快(不用跳轉) |
| **適合用途** | 適合重複使用但不要求速度的程式 | 適合小段程式且要求速度的情況 |
---
### **4. 小結**
- **副程式** 是一個「可以呼叫的程式」,適合重複使用但不佔太多空間。
- **巨集** 是「展開的程式碼」,不需要跳轉但會讓程式變長。
- 何時使用?**如果要節省記憶體,選副程式;如果要更快執行,選巨集。**
這樣你能夠理解副程式與巨集的不同了嗎?
## **副程式 (Subroutine) 與巨集 (Macro) 的完整比較教學**
在微處理機程式設計中,「副程式 (Subroutine)」和「巨集 (Macro)」是兩種**提高程式碼重用性**的重要技術。兩者的主要目標都是**減少程式碼重複、提升可讀性與維護性**,但它們的運作方式、效率與應用場景卻有所不同。本文將透過**定義、運作方式、優缺點比較、適用情境與實作範例**來幫助學生完整理解這兩種技術。
---
## **1. 副程式 (Subroutine)**
### **(1) 副程式的定義**
副程式是一段可重用的程式碼,具有**獨立的程式區塊**,可在主程式中透過**CALL 指令**呼叫,執行完畢後再透過**RETURN 指令**返回主程式。副程式可以減少程式碼重複,使程式結構更清晰。
### **(2) 副程式的運作方式**
- 當程式執行到 **CALL 指令**,會將**返回位址**存入**堆疊 (Stack)**,然後跳轉到副程式執行。
- 副程式執行完畢後,遇到 **RETURN 指令 (RET)**,會從堆疊中取回返回位址,繼續執行主程式的後續指令。
### **(3) 副程式的優缺點**
✅ **優點**:
- **節省記憶體**:程式碼只存放一次,每次呼叫時只需跳轉,不會重複插入程式碼。
- **適用於大型程式**:可拆解為多個模組,提高可讀性與維護性。
- **支援參數傳遞**:可使用暫存器或堆疊來傳遞不同的輸入值,使副程式更靈活。
❌ **缺點**:
- **執行時會有額外的時間開銷**:呼叫副程式時需要 **壓入與彈出堆疊 (PUSH/POP)**,會消耗 CPU 時間。
- **需要額外的堆疊空間**:如果頻繁呼叫副程式,可能導致 **堆疊溢位 (Stack Overflow)**。
### **(4) 副程式的範例**
🔹 **使用 CALL/RET 指令的副程式 (8086 組合語言)**
```assembly
MAIN:
MOV AX, 5 ; 將 5 放入 AX
MOV BX, 10 ; 將 10 放入 BX
CALL ADDITION ; 呼叫副程式 ADDITION
HLT ; 停止執行
ADDITION:
ADD AX, BX ; AX = AX + BX
RET ; 返回主程式
```
這段程式執行後,AX 內的值會變成 15,然後回到 `MAIN` 繼續執行。
---
## **2. 巨集 (Macro)**
### **(1) 巨集的定義**
巨集是一段 **可重用的指令集合**,在**組譯 (Assembly) 時**,會被展開並**直接插入程式碼中**。這與副程式不同,因為副程式的程式碼只存在一份,而巨集的程式碼則會被展開並重複插入每個使用的地方。
### **(2) 巨集的運作方式**
- **在組譯 (Compile) 時期執行展開**,而不是在執行時才呼叫,因此不會有 **CALL/RET 指令** 的時間開銷。
- 每次使用巨集時,組譯器會將其**完整展開**,使程式碼變長,但執行時不會有副程式呼叫的額外開銷。
### **(3) 巨集的優缺點**
✅ **優點**:
- **執行速度較快**:因為是**直接展開**,不需要 CALL/RET 指令,也不會影響堆疊。
- **適用於小型、簡單的功能**:例如單純的 I/O 指令、數學運算等。
❌ **缺點**:
- **增加程式碼大小**:每次使用巨集,組譯器都會展開一次,導致程式碼變長 (Code Bloat)。
- **不適合複雜功能**:巨集不支援堆疊處理與參數傳遞,功能相對簡單。
### **(4) 巨集的範例**
🔹 **使用巨集的程式 (8086 組合語言)**
```assembly
MACRO ADDITION X, Y
MOV AX, X
ADD AX, Y
ENDM
MAIN:
ADDITION 5, 10 ; 巨集展開後會變成 MOV AX, 5 / ADD AX, 10
HLT ; 停止執行
```
在組譯時,這段程式碼會直接展開,等效於:
```assembly
MAIN:
MOV AX, 5
ADD AX, 10
HLT
```
可以看到,巨集的**程式碼直接插入**,不需要 CALL/RET。
---
## **3. 副程式與巨集的比較**
| **比較項目** | **副程式 (Subroutine)** | **巨集 (Macro)** |
|--------------|------------------|--------------|
| **定義方式** | 透過 `CALL` 指令呼叫,使用 `RET` 返回 | 直接在組譯時展開 |
| **執行效率** | 需要 `CALL` 和 `RET`,有額外的 CPU 開銷 | 無需呼叫,執行速度較快 |
| **記憶體使用** | 只存一次,呼叫時跳轉,不會重複佔用空間 | 每次使用都會展開,程式碼變長 |
| **適用場景** | 適合大型程式、需多次重用的功能 | 適合小型功能,如 I/O、簡單計算 |
| **支援參數傳遞** | 可透過堆疊或暫存器傳遞參數 | 參數在組譯時展開,無法動態變更 |
| **維護性** | 只需修改副程式本體 | 需要修改所有展開的程式碼 |
---
## **4. 什麼時候該用副程式?什麼時候該用巨集?**
✅ **使用副程式的情境**:
- **需要多次重用** (如數學運算、I/O 處理)
- **程式碼較長,無法重複插入** (如驅動程式、通訊協定)
- **需要參數傳遞與返回值**
✅ **使用巨集的情境**:
- **程式碼簡短,執行效率重要** (如 LED 開關控制)
- **指令較少,避免 CALL/RET 的額外負擔** (如組態設定)
---
## **5. 總結**
1. **副程式 (Subroutine)** 透過 CALL/RET 呼叫,**節省記憶體但執行速度較慢**,適合**大型程式與需多次呼叫的功能**。
2. **巨集 (Macro)** 在組譯時直接展開,**執行速度快但會增加程式碼大小**,適合**短小的功能模組**。
3. **副程式適合動態運行,巨集適合靜態展開**,在程式設計時應根據需求選擇合適的方法。
透過這樣的比較,大學生可以清楚理解副程式與巨集的差異,並在程式設計時作出正確選擇。