简单的C语言解释运行器实现(六)—— 生成指令

本文详细介绍了如何实现一个简单的C语言解释器,重点在于生成指令和模拟执行过程。通过操作数栈、本地变量表、指令集等概念,阐述了如何处理变量、函数调用、数组及可变参数。文中还提到了内建函数的处理和运行时检查,以确保指令的正确执行。最后,讨论了如何将这个解释器扩展到支持函数递归调用和生成汇编代码的可能性。
摘要由CSDN通过智能技术生成

上一篇:语义分析

执行指令

在我们有了带类型的语法树后,我们就可以生成指令了。如果你有读过Java虚拟机规范的话,可以看到庞大的JVM指令集,好像是近200条?接下来我们要考虑代码的执行过程并生成指令。

首先我们考虑代码的执行过程,我们知道程序运行是基于栈的,那么具体是怎么运行的呢?开篇我们提到了这样的一个语法树:

   S
  /|\
 F = E
 |  /|\
 i F + T
   |  /|\
   i F * F
     |   |
     2   i

这个是关于语句x = a + 2 * c的,我介绍了生成的指令是

iload a
iconst 2
iload c
imul
iadd
istore x

这样的(应该和JVM规范比较类似)。

我们可以看到有这样的一些指令:constloadmuladdstore。我首先介绍他们的意义分别是向栈中压入一个常量、向栈中压入一个变量的值、在栈顶取出两个值做乘法再将值压入栈,在栈顶取出两个值做加法再将值压入栈,将栈顶的值存入变量。我们看到loadconststore指令还可以带参数。

你应该注意到了我实际调用的指令都有前缀i,这表示指令是操作int整型变量,因为我们要加快实际运行的速度,我们不应该在调用运行的时候才来判断指令要操作的操作数的类型。因此我们在生成指令的时候可以根据语义分析得到的类型生成对应的指令,这也是我们在语义分析的时候就把隐式类型转换转化为强制类型转换的原因,如果我们对任意两种基本类型都来一种指令,那指令数量就爆炸了。而强制类型转换的指令数并不会很多。

接下来我们模拟一下这段指令的执行过程(假设c=4;a=5):

<空>  // 栈初始为空,左侧为栈底,右侧为栈顶
5     // 执行了iload a,因为a = 5,所以栈顶压入5
5 2   // 执行了iconst 2,栈顶压入2
5 2 4 // 执行了iload c,因为c = 4,所以栈顶压入4
5 8   // 执行了imul,栈顶取出2个元素2和4,做乘法得到8,重新压入栈
13    // 执行了iadd,栈顶取出2个元素5和8,做加法得到13,重新压入栈
<空>  // 执行了istore x,栈顶取出13,赋给x,现在x就等于13了

执行完一段有意义的语句后栈就应该清空,比如我们有这样的一个语句func(1, 2);。假设func的返回值为int,那么我们生成的指令应该是这样的:

iconst 1
iconst 2
invoke_static #func

其中invoke_static表示调用静态(全局)的函数,并将栈顶的参数弹出,执行#func,将函数的返回值压入栈。假设func(1, 2) = 3,那么栈有:

<空> 
1   // 执行了iconst 1
1 2 // 执行了iconst 2
3   // 执行了invoke_static #func

因为func的返回值永远不会被使用到,所以我们还需要将多余的元素弹出。比如加一个指令pop表示弹出栈顶元素。
也许你会问不弹出会怎么样,如果我们有这样的一个语句:

i + (1 + (func(1, 2), 1));

由于逗号表达式的特性,只返回最右的那个分句的值,因此前面分句的值是无用的。生成的指令可能这样:

i       // iload i
i 1     // iconst 1
i 1 1   // iconst 1
i 1 1 2 // iconst 2
i 1 3   // invoke_static #func
i 1 3 1 // iconst 1
i 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值