项目的完整代码在 C2j-Compiler https://github.com/dejavudwh/C2j-Complier
前言
第十一篇, 终于要进入代码生成部分了, 但是但是在此之前, 因为我们要做的是 C 语言到字节码的编译, 所以自然要了解一些字节码, 但是由于 C 语言比较简单, 所以只需要了解一些字节码基础
JVM 的基本机制
JVM 有一个执行环境叫做 stack frame
这个环境有两个基本数据结构
执行堆栈: 指令的执行, 都会围绕这个堆栈来进行
局部变量数组, 参数和局部变量就存储在这个数组.
还有一个 PC 指针, 它指向下一条要执行的指令.
举一个例子
- int f(int a, int b) {
- return a+b;
- }
- f(1,2);
JVM 的执行环境是这样变化的
- stack:
- localarray:1,2
pc: 把 a 从 localarray 取出放到 stack
- stack:1
- localarray:2
pc: 把 b 从 localarray 取出放到 stack
- stack:1,2
- localarray:
pc: 把 a,b 弹出堆栈并且相加压入堆栈
对于 JVM 提供的对象
- .class public CSourceToJava
- .super java/lang/Object
- .method public static main([Ljava/lang/String;)V
- getstatic java/lang/System/out Ljava/io/PrintStream;
- ldc "Hello World!"
- invokevirtual java/io/PrintStream/println(Ljava/lang/String;)V
- return
- .end method
- .end class
getstatic,ldc 和 invokevirtual 都相当于 JVM 提供的指令
getstatic 和 ldc 相当于压入堆栈操作. invokevirtual 则是从堆栈弹出参数, 然后调用方法
stack: out "Hello World!"
JVM 的基本指令
pusu load store
JVM 的运行基本都是围绕着堆栈来进行, 所以指令也都是和堆栈相关, 比如进行一个乘法 1 * 2:
- bipush 1
- bipush 2
- imul
可以看到 JVM 的指令操作时带数据的类型, b 代表 byte, 也就是只能操作 - 128 ~ 128 之间的数, 而 i 代表是整形操作, 所以相应也会有 sipush 等等了
下面加入要把 1 * 2 打印用 prinft 打印在控制台上, 就需要把 out 对象压入堆栈, 此时的堆栈:
stack: 2 out
但是调用 out 的参数需要在堆栈顶部, 所以这时候就需要两个指令 iload,istore
istore 0 把 2 放到局部变量队列, 再把 out 压入堆栈, 再用 iload 0 把 2 放入堆栈中
stack: out 2
局部变量和函数参数
局部变量
在字节码里, 局部变量和函数参数都会存储在队列上
- int func() {
- int a;
- int b;
- a = 1;
- b = 2;
- return a + b;
- }
看一下这个方法执行的时候堆栈的变化情况
- // 执行 a = 1, 把 1 压到 stack 上, 再把 1 放入到队列里
- stack:
- array:1
- // 执行 b = 1, 也同理
- stack:
- array:1, 2
最后的 return 也有相应的 return 指令, 所以完整的指令如下
- sipush 1
- istore 0
- sipush 2
- istore 1
- iload 0
- iload 1
- iadd
- ireturn
函数参数
int func(int a, int b, int c, int d){}
在调用这个函数的适合, 函数参数就会按照顺序被压入堆栈中, 然后拷贝到队列上
- stack: a b c d
- array:
- stack:
- array: d c b a
所以在之后的代码生成部分就需要一个来找到局部变量的位置的函数
数组
创建数组
下面这段指令的作用是创建一个大小为 100 的整形数组
- sipush 100
- newarray int
- astore 0
sipush 100 把元素个数压入堆栈
newarray int 创建一个数组, 后面是数据类型
astore 表示把数组对象移入队列 a 表示的是一个对象引用
读取数组
下面这段指令是读取数组的第 66 个元素
- aload 0
- sipush 66
- iaload
aload 0 把数组对象放到堆栈上
sipush 放入要读取的元素下标
iaload 把读取的值压入堆栈
元素赋值
- aload 0
- sipush 7
- sipush 10
- iastore
aload 0 把数组对象加载到堆栈
sipush 7 把要赋值的值压入堆栈
sipush 10 把元素下标压入堆栈
iastore 进行赋值
结构体
C 语言里的结构体其实就相当于没有方法只有属性的类, 所以可以把结构体编译成一个类
创建一个类
- new MyClass // 创建一个名字为 MyClass 的类
- invokespecial ClassName/<init>() V // 调用类的无参构造函数
例子
- public class MyClass {
- public int a;
- public char c;
- public MyClass () {
- this.a = 0;
- this.c = 0;
- }
- }
public class MyClass 生成下面的代码, 都是对应生成一个类的特殊指令
- .class public MyClass
- .super java/lang/Object
下面的则是对应属性的声明
- .field public c C
- .field public a I
声明完属性, 就是构造函数了, 首先是先把类的实例加载到堆栈, 再调用它的父类构造函数, 对属性的赋值:
加载类的实例到堆栈上 aload 0
压入值 sipush 0
赋值的对应指令 putfield MyClass/c C
- aload 0
- invokespecial java/lang/Object/<init>()V
- aload 0
- sipush 0
- putfield MyClass/c C
- aload 0
- sipush 0
- putfield MyClass/a I
- return
完整的对应的 Java 字节码如下:
- .class public MyClass
- .super java/lang/Object
- .field public c C
- .field public a I
- .method public <init>()V
- aload 0
- invokespecial java/lang/Object/<init>()V
- aload 0
- sipush 0
- putfield MyClass/c C
- aload 0
- sipush 0
- putfield MyClass/a I
- return
- .end method
- .end class
读取类的属性
aload 3 ; 假设类实例位于局部变量队列第 3 个位置
putfield ClassName/x I
小结
这一篇主要就是了解一下 Java 基本的字节码, 因为 C 语言的语法比较简单, 所以只需要知道一点就足够生成代码了. 下一篇就可以正式进入代码生成部分
另外, 欢迎 Star 这个项目!
来源: https://www.cnblogs.com/secoding/p/11384619.html