项目的完整代码在 C2j-Compilerjava
第十一篇,终于要进入代码生成部分了,可是可是在此以前,由于咱们要作的是C语言到字节码的编译,因此天然要了解一些字节码,可是因为C语言比较简单,因此只须要了解一些字节码基础git
JVM有一个执行环境叫作stack framegithub
这个环境有两个基本数据结构数组
还有一个PC指针,它指向下一条要执行的指令。数据结构
int f(int a, int b) { return a+b; } f(1,2);
JVM的执行环境是这样变化的jvm
stack: localarray:1,2 pc:把a从localarray取出放到stack
stack:1 localarray:2 pc:把b从localarray取出放到stack
stack:1,2 localarray: pc:把a,b弹出堆栈而且相加压入堆栈
.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则是从堆栈弹出参数,而后调用方法oop
stack: out "Hello World!"
JVM的运行基本都是围绕着堆栈来进行,因此指令也都是和堆栈相关,好比进行一个乘法1 * 2:this
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
下面这段指令是读取数组的第66个元素
aload 0 sipush 66 iaload
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 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
下面的指令建立了10个字符串类型的数组,这时候堆栈上的对象是一个引用,指向heap上一个10个字符串类型的数组
sipush 10 anewarray java/lang/String
下面的指令则是对数组的第一个元素进行赋值
astore 0 aload 0 sipush 0 ldc "hello world" aastore
因此对于咱们本身定义的类也是同样的
sipush 10 anewarray MyClass astore 0
下面则是对数组第一个下标生成一个MyClass对象
aload 0 sipush 1 new MyClass invokespecial CTag/<init>()V aastore
下面是对数组里的对象的属性的取值和赋值操做,只是组合了以前的指令而已
aload 0 sipush 1 aaload sipush 1 putfield MyClass/x I aload 0 sipush 1 aaload getfield MyClass/x I
JVM指令还有两个个很是重要的指令就是分支和循环指令,咱们先来看分支指令
if (1 < 2) { a = 1; } else { a = 2; }
上面对应的JVM指令以下:
sipush 1 sipush 2 if_cmpge branch0 sipush 1 astore 0 goto out_branch0 branch0: sipush 2 istore 0 out_branch0: sipush 3 istore 0
基本的JVM指令只剩循环语句了,逻辑也不困难,基本的JVM指令相对于汇编算是很是简单了
for (i = 0; i < 3; i++) { a[i] = i; }
上面生成的对应字节码以下(假设如今变量i在队列的第5个位置,a在队列的第2个位置):
sipush 0 istore 5 loop0: iload 5 sipush 3 if_icmpge branch0 aload 2 ;加载数组 iload 3 ;加载标i iload 3 ;加载变量i iastore ;把i的值存入到a[i] iload 3 ;加i sipush 1 ;把1压入堆栈 iadd ;i++ istore 3 ;把i+1后的值放入到i的队列上的位置 goto loop0 ;跳转到循环开头 branch0:
这一篇主要就是了解一下Java基本的字节码,由于C语言的语法比较简单,因此只须要知道一点就足够生成代码了。因此相对于汇编来讲,是很是简单的了。这样下一篇就能够正式进入代码生成部分
另外,欢迎Star这个项目!