副程式與巨集:簡單明瞭的比較** 在微處理機的程式設計中,我們會發現有些程式碼會重複出現,這時我們可以使用 **副程式** 或 **巨集** 來簡化程式,讓它更容易閱讀和管理。那麼,這兩者有什麼不同呢?讓我們用簡單的方式來理解! --- ### **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. **副程式適合動態運行,巨集適合靜態展開**,在程式設計時應根據需求選擇合適的方法。 透過這樣的比較,大學生可以清楚理解副程式與巨集的差異,並在程式設計時作出正確選擇。