GitHub 專案提供完整原始碼供參考對照:虛擬機教學專案。本文將帶領你以 C 語言從頭實作一個簡易的堆疊式虛擬機,深入理解編譯器、直譯器與虛擬機的底層運作原理。
本虛擬機採用堆疊式架構,擁有 6 個通用暫存器(A–F)與一個程式計數器(IP)。指令集精簡如下:
PSH 5 ; 將 5 推入堆疊 PSH 10 ; 將 10 推入堆疊 ADD ; 彈出兩值相加,結果推回堆疊 POP ; 彈出堆疊頂端並列印(除錯用) SET A 0 ; 設定暫存器 A = 0 HLT ; 停止程式執行
程式以整數陣列儲存,例如 {PSH, 5, PSH, 6, ADD, POP, HLT} 即完成 5 + 6 並輸出結果。
虛擬機主迴圈極為精簡:
while (running) {
eval(fetch());
ip++;
}
fetch():根據 ip 從程式陣列取得當前指令。eval():以 switch 解析指令並執行對應邏輯。以固定大小陣列 int stack[256] 模擬堆疊,sp(Stack Pointer)初始為 -1 表示空堆疊。
stack[++sp] = operand;val = stack[sp--];stack[++sp] = b + a;(注意順序影響減法/除法)。以 enum 定義暫存器索引,並以陣列 registers[NUM_OF_REGISTERS] 儲存值。進階技巧:將 ip、sp 也納入暫存器陣列,透過巨集 #define ip (registers[PC]) 實現統一存取,便於實作跳轉指令(如 JMP、JZ)。
~/Dev/mac/
├── Makefile
└── src/
└── main.c
Makefile 範例:
SRC_FILES = main.c
CC_FLAGS = -Wall -Wextra -g -std=c11
CC = clang
all:
${CC} ${SRC_FILES} ${CC_FLAGS} -o mac
執行 make 編譯,再執行 ./mac 即可看到虛擬機輸出 popped 11。
MOV A, B、SET REG VALUE 指令。JZ offset(堆疊頂為 0 時跳轉)。bettervm.c 了解更完整實作。