呵呵,我又来了~~
看看上次写编译原理学习日记的日期,2月2日。汗~~~已经一个月了……不过,这一个月我可没闲着。我真没想到,做这么一个小玩意居然也要很多数学知识,可能是我想把它做的完善些,为它添加了很多三角函数指令。可是,C标准库里的数学运算函数十分有限,因此,我不得不用一些数学方法来用有限的这些函数实现所有期望的运算。而我的学习……
不过幸运的是,在好多哥们的鼓励和一些热情的高手的指点下,我现在已经学会了这些数学方法,也完成了这个虚拟机!
首先,我对指令系统进行了完善,主要是添加了很多三角函数指令,尤其是双曲函数。还有,我修改了操作数和指令的数据类型,通过使用typedef语句,我将操作数类型声明为double、将指令类型声明为unsigned char。这主要体现在inst.h的改动中,我在其中添加了如下声明:
/* type of instructions: */
typedef unsigned char inst_t;
/* type of operands: */
typedef double operand_t;
这使得我的程序更具伸缩性。当然,这些改动使得我同时改动了汇编编译器和反汇编编译器。如果大家真的感兴趣,而且需要“新版”的计算器,呵呵,老规矩,给我发邮件,我悄悄地告诉你。
另外还有一件值得高兴的事我要在这里说,我的大学英语四级(国家)考试居然过了!我还是上上次(非典刚过,9月份那次)考的试,都没指望了,也没查过分,而且上次(大概是1月份的吧)都没报名。前两天居然接到通知,60.5!哈哈哈~~~
好了,这也不是什么光彩的事,不过觉得很有意思而已。我这种人四级居然能过,中国的教育……
现在就说说我这个虚拟的计算器的实现吧。程序方面没什么新鲜的,主要是一些想法和其中的数学原理。
首先,以前已经说过,这个计算器是基于堆栈的。不过,我没有在其中使用任何显式的堆栈数据结构,也没有特地为此建立函数库。原因很简单——效率。我在程序中声明了一个全局数组opStack,类型为operand_t,作为运算栈;以及一个全局变量opSP,类型为unsigned int,作为堆栈指针。堆栈指针的初值为零(因为使用了无符号整型),这可能与一些用数组实现堆栈的程序不太一样;这样,出栈操作类似于:
operand_t operand = opStack[--opSp];
而入栈操作类似于:
opStack[opSp++] = operand;
也就是说,我的堆栈指针并未指向栈顶元素,而是指向了栈顶元素上面的位置。不过,我在程序中并没有使用这样的操作,稍后将作说明。
由于使用了固定大小的运算栈,对堆栈的空满状态的检查就显得尤为重要。因为几乎所有的指令都要涉及到出入栈操作,因此,我为栈状态的检查编写了两个宏:
/* check the stack status(overflow): */
#define check_overflow() \
if(opSp >= MAX_STACK_SIZE - 1) { \
fprintf(stderr, "Operate stack overflow!\n"); \
exit(0); \
}
/* check the stack statuc(underflow)
* n means how many operands are required:
*/
#define check_underflow(n) \
if(opSp <= n - 1) { \
fprintf(stderr, "Operate stack underflow!\n"); \
exit(0); \
}
其中,check_overflow()用于检查运算栈是否上溢出,用于所有入栈操作之前;check_underflow()用于检查运算栈是否下溢出,用于所有出栈操作之前。由于所有的指令最多只会将一个操作数压入堆栈,check_overflow()宏是没有参数的,只要检查运算栈中是否有一个空位即可;但是,有的指令需要弹出一个操作数,有的需要两个,所以我为check_underflow()宏添加了一个参数,用来表示需要的检查的最小空间。下面的示意图显示了堆栈的结构和堆栈指针在不同情况下的位置,可以帮助你理解如果获取栈顶操作数(后面会遇到)以及上、下溢出检查的依据。