基于堆栈的虚拟机实现
前面我們對一基于堆棧虛擬機進行了源碼剖析《基于棧的虛擬機源碼剖析》。之前我們也實現了一個簡單的基于堆棧的虛擬機《實現一個堆棧虛擬機》。在《實現一個堆棧虛擬機》中,我們將虛擬機定義為一個VirtualBox類,VirtualBox類中有成員變量:堆棧、指令內存、數據內存,另外還有成員函數:讀取指令、執行指令。《基于棧的虛擬機源碼剖析》中,是C語言實現的,沒有設計成類的形式,但依然有堆棧、指令、數據、讀取指令、執行指令等模塊。
???????? 這里,我們再次實現一個基于堆棧的虛擬機。先給出實現代碼,然后再對代碼進行解釋。
// 基于堆棧的虛擬機實現 #include <iostream> #include <string> #include <vector> #include <stack> using namespace std;// 虛擬機的二進制指令集 enum Command {HALT, IN, OUT, ADD, SUB, MUL, DIV,INMEMORY, /* 存放內存 */OUTMEMORY, /* 讀取內存 */DUP,LD, ST, LDC, JLT, JLE, JGT, JGE, JEQ, JNE, JMP,INVALID };// 指令結構體 struct Instruction {Command com; // 指令碼int opd; // 操作數 };// 堆棧 stack<int> stk;// 指令內存 vector<Instruction> insMemory;// 數據內存 vector<int> datMemory(101);// 狀態碼 enum StateCode {scHALT, scOK,errDIVBYZERO, errDATMEMORY, errINSMEMORY,errSTACKOVERFLOW, errSTACKEMPTY, errPOP,errUNKNOWNOP };// 輸出錯誤信息 void Error(const string& err) {cerr << err << endl; }// 根據指令碼返回指令碼需要的操作數個數 int GetOperandCount(Command com) {int ret = 0;switch (com){case INMEMORY:case OUTMEMORY:case LDC:case JLT:case JLE:case JGT:case JGE:case JEQ:case JNE:case JMP:ret = 1;break;default:ret = 0;break;}return ret; }// 讀取指令 // 從字符串中讀取指令碼和操作數 // 這里讀取的是文本,而非二進制 // 所以指令碼占2位,操作數占4位 void ReadInstruction(const string& strCodes) {int idx = 0;Instruction ins;Command com;int opd;while (idx < strCodes.size()){string strCod = strCodes.substr(idx, 2);idx += 2;cout << strCod;com = static_cast<Command>(atoi(strCod.c_str())); // 字符轉換為指令碼,不檢測com是否合法int cnt = GetOperandCount(com);if (cnt > 0){string strOpd = strCodes.substr(idx, 4);idx += 4;opd = atoi(strOpd.c_str());cout << '\t' << strOpd;}else{opd = 0;}cout << endl;ins.com = com;ins.opd = opd;insMemory.push_back(ins);} }// 執行指令 void ExecuteInstructions() {for (auto idx = 0; idx < insMemory.size() && insMemory[idx].com != HALT; /*++idx*/){bool idxChg = false;int idxJump = 0;switch (insMemory[idx].com){case HALT: // 終止break;case IN: // 輸入 {int tmp;cout << "輸入:" << endl;cin >> tmp;stk.push(tmp);}break;case OUT: // 輸出 {int tmp = stk.top();stk.pop();cout << tmp << endl;}break;case ADD: // 加法 {int a = stk.top();stk.pop();int b = stk.top();stk.pop();int c = b + a;stk.push(c);}break;case SUB: // 減法 {int a = stk.top();stk.pop();int b = stk.top();stk.pop();int c = b - a;stk.push(c);}break;case MUL: // 乘法 {int a = stk.top();stk.pop();int b = stk.top();stk.pop();int c = b * a;stk.push(c);}break;case DIV: // 除法 {int a = stk.top();stk.pop();int b = stk.top();stk.pop();if (a == 0){Error("除數為0");// 忽略 }else{int c = b / a;stk.push(c);}}break;case INMEMORY:{int addr = insMemory[idx].opd;if (addr < 0 || addr >= datMemory.size()){Error("數據地址錯誤");// 忽略處理 }else{datMemory[addr] = stk.top();stk.pop();}}break;case OUTMEMORY:{int addr = insMemory[idx].opd;if (addr < 0 || addr >= datMemory.size()){Error("數據地址錯誤");// 忽略處理 }else{stk.push(datMemory[addr]);}}break;case DUP: // 將棧頂元素的值重復壓棧 {stk.push(stk.top());}break;case LD: // 彈出棧頂元素值,以值為地址,將該地址上的值壓棧 {int addr = stk.top();stk.pop();if (addr < 0 || addr >= datMemory.size()){Error("地址錯誤");// 忽略錯誤 }else{stk.push(datMemory[addr]);}}break;case ST: // 彈出值,再彈出地址,將值賦予該地址 {int val = stk.top();stk.pop();int addr = stk.top();stk.pop();if (addr < 0 || addr >= datMemory.size()){Error("地址錯誤");// 忽略錯誤 }else{datMemory[addr] = val;}}break;case LDC: // 該指令有參數,將該參數壓入棧中 {stk.push(insMemory[idx].opd);}break;case JLT: // 該指令有參數,如果從棧中彈出的值小于0,則指令指針寄存器跳轉到操作數 {int tmp = stk.top();stk.pop();if (tmp < 0){idxChg = true;idxJump = insMemory[idx].opd;}}break;case JLE: // <= {int tmp = stk.top();stk.pop();if (tmp <= 0){idxChg = true;idxJump = insMemory[idx].opd;}}break;case JGT: // > {int tmp = stk.top();stk.pop();if (tmp > 0){idxChg = true;idxJump = insMemory[idx].opd;}}break;case JGE: // >= {int tmp = stk.top();stk.pop();if (tmp >= 0){idxChg = true;idxJump = insMemory[idx].opd;}break;}case JEQ: // == {int tmp = stk.top();stk.pop();if (tmp == 0){idxChg = true;idxJump = insMemory[idx].opd;}}break;case JNE: // != {int tmp = stk.top();stk.pop();if (tmp != 0){idxChg = true;idxJump = insMemory[idx].opd;}}break;case JMP: // 無條件 {idxChg = true;idxJump = insMemory[idx].opd;}break;default:{Error("未知指令碼");}break;}if (idxChg){idx = idxJump;}else{++idx;}} }// 復位虛擬機 void Reset() {insMemory.clear();datMemory.clear();datMemory.resize(101);while (!stk.empty()){stk.pop();} }int main() {// 測試虛擬機// 測試:// 輸入兩個數,并將其比較,將較大者輸出//00 01 #輸入第一個數 IN//01 01 #輸入第二個數 IN//02 07 0001 #將數2存入到內存1中 INMEMORY//03 07 0000 #將數1存入到內存0中 INMEMORY//04 08 0000 #重新將內存0數入棧 OUTMEMORY//05 08 0001 #重新將內存1數入棧 OUTMEMORY//06 04 #將數1減去數2,數1和數2都彈棧,并將結果入棧 SUB//07 15 0011 #檢測棧頂元素是否大于0,如果大于0進行跳轉 JGT//08 08 0001 #如果不大于0,則將內存1輸出,首先還是將內存1數入棧 OUTMEMORY//09 02 #將數1輸出 OUT//10 19 0013 #無條件跳轉到終止 JMP//11 08 0000 #如果大于0,則將內存0輸出,首先還是將內存0數入棧 OUTMEMORY//12 02 #將數0輸出 OUT//13 00 #終止 HALT// 輸入指令為:// 010107000107000008000008000104150011080001021900130800000200while (true){string strCodes;cin >> strCodes;ReadInstruction(strCodes);ExecuteInstructions();Reset();}return 0; }
? ? ? ? ?上面基于堆棧的虛擬機主要包含以下幾部分:
???????? 1.虛擬機的二進制指令集定義
???????? 2.指令結構體定義
???????? 3.堆棧
???????? 4.指令內存
???????? 5.數據內存
???????? 6.狀態碼的定義
???????? 7.錯誤信息的處理
???????? 8.根據指令碼獲取其操作數個數
?????????9.讀取指令,這里我們是從文本中讀取的指令碼和操作數,而不是從二進制數據中讀取。由于指令碼的個數大于9,所以我們的指令碼占2位,另外操作數占據4位,操作數可以是數據內存的地址,也可以是指令內存的地址,或者是具體的數值。
???????? 10.執行指令
???????? 11.復位
???????? 12.測試指令:
???????? 這里我們的測試樣例是:輸入兩個數,將其比較輸出其中較大的哪個數,指令如下:
00 01 #輸入第一個數 IN 01 01 #輸入第二個數 IN 02 07 0001 #將數2存入到內存1中 INMEMORY 03 07 0000 #將數1存入到內存0中 INMEMORY 04 08 0000 #重新將內存0數入棧 OUTMEMORY 05 08 0001 #重新將內存1數入棧 OUTMEMORY 06 04 #將數1減去數2,數1和數2都彈棧,并將結果入棧 SUB 07 15 0011 #檢測棧頂元素是否大于0,如果大于0進行跳轉 JGT 08 08 0001 #如果不大于0,則將內存1輸出,首先還是將內存1數入棧 OUTMEMORY 09 02 #將數1輸出 OUT 10 19 0013 #無條件跳轉到終止 JMP 11 08 0000 #如果大于0,則將內存0輸出,首先還是將內存0數入棧 OUTMEMORY 12 02 #將數0輸出 OUT 13 00 #終止 HALT輸入指令為: 010107000107000008000008000104150011080001021900130800000200? ? ? ?
? ? ? ? ?另外,對于JLT、JLE、JGT、JGE、JEQ、JNE、JMP這幾個跳轉指令,其操作數是為下一個執行指令的地址,而非當前指令地址的增量。
?
???????? 以上是我們根據《基于棧的虛擬機的實現》,相對于之前的《實現一個堆棧虛擬機》臨摹的另一個版本的堆棧虛擬機。堆棧虛擬機支持更多的指令,比如算術指令、函數操作等有待我們進一步學習。另外,基于寄存器的虛擬機實現原理將在以后的學習中予以介紹。除此之外,還會研讀一些別人的源碼(比如XML解析器的實現),從中學習一些東西。
總結
以上是生活随笔為你收集整理的基于堆栈的虚拟机实现的全部內容,希望文章能夠幫你解決所遇到的問題。