另一版本的基于堆栈的虚拟机实现
另一版本的基于堆栈的虚拟机实现
前面我们对一基于堆栈虚拟机进行了源码剖析《基于栈的虚拟机源码剖析》。之前我们也实现了一个简单的基于堆栈的虚拟机《实现一个堆栈虚拟机》。在《实现一个堆栈虚拟机》中,我们将虚拟机定义为一个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 // 输入指令为: // 010107000107000008000008000104150011080001021900130800000200 while (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解析器的实现),从中学习一些东西。
(完)
文档信息
·版权声明:自由转载-非商用-非衍生-保持署名 | Creative Commons BY-NC-ND 3.0
·博客地址:http://www.cnblogs.com/unixfy
·博客作者:unixfy
·作者邮箱:goonyangxiaofang(AT)163.com
·如果你觉得本博文的内容对你有价值,欢迎对博主 小额赞助支持