项目的完整代码在 C2j-Compiler

前言

第十一篇,终于要进入代码生成部分了,但是但是在此之前,因为我们要做的是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

声明完属性,就是构造函数了,首先是先把类的实例加载到堆栈,再调用它的父类构造函数,对属性的赋值:

  1. 加载类的实例到堆栈上 aload 0
  2. 压入值 sipush 0
  3. 赋值的对应指令 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

结构体数组

下面的指令创建了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指令如下:

  • 先把1和2压入堆栈
  • if_cmpge指令是大于等于,即如果1大于等于2就去执行else分支
  • goto指令是跳转到相应的标签,也就是执行完if,就跳出else部分
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个位置):

  • 首先对i赋值
  • 再把3压入堆栈和i做比较,判断i < 3
  • 之后就是对数组的操作
  • 然后修改i的值
  • 返回loop0继续判断i < 3
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这个项目!

从零写一个编译器(十一):代码生成之Java字节码基础的更多相关文章

  1. 从零写一个编译器(十三):代码生成之遍历AST

    项目的完整代码在 C2j-Compiler 前言 在上一篇完成对JVM指令的生成,下面就可以真正进入代码生成部分了.通常现代编译器都是先把生成IR,再经过代码优化等等,最后才编译成目标平台代码.但是时 ...

  2. 从零写一个编译器(九):语义分析之构造抽象语法树(AST)

    项目的完整代码在 C2j-Compiler 前言 在上一篇完成了符号表的构建,下一步就是输出抽象语法树(Abstract Syntax Tree,AST) 抽象语法树(abstract syntax ...

  3. 手把手教你从零写一个简单的 VUE

    本系列是一个教程,下面贴下目录~1.手把手教你从零写一个简单的 VUE2.手把手教你从零写一个简单的 VUE--模板篇 今天给大家带来的是实现一个简单的类似 VUE 一样的前端框架,VUE 框架现在应 ...

  4. 手把手教你从零写一个简单的 VUE--模板篇

    教程目录1.手把手教你从零写一个简单的 VUE2.手把手教你从零写一个简单的 VUE--模板篇 Hello,我又回来了,上一次的文章教会了大家如何书写一个简单 VUE,里面实现了VUE 的数据驱动视图 ...

  5. 打造一个简单的Java字节码反编译器

    简介 本文示范了一种反编译Java字节码的方法,首先通过解析class文件,然后将解析的结果转成java代码.但是本文并没有覆盖所有的class文件的特性和指令,只针对部分规范进行解析. 所有的代码代 ...

  6. 学了编译原理能否用 Java 写一个编译器或解释器?

    16 个回答 默认排序​ RednaxelaFX JavaScript.编译原理.编程 等 7 个话题的优秀回答者 282 人赞同了该回答 能.我一开始学编译原理的时候就是用Java写了好多小编译器和 ...

  7. 从零写一个Asp.net core手脚架(模型验证)

    一个asp.net core项目,一定包含了各种的实体,在RESTful api里面,有很多的参数传递,不建立实体则大量的参数需要自定验证正确性,并且Action上面会写的密密麻麻的参数 在asp.n ...

  8. 从零写一个Asp.net core手脚架 (异常处理)

    既然是手脚架,那么肯定得明白,手脚架是有限资源的一个整合,我们尽可能完善它,并保留可扩展性才是最终目的,尽可能减少硬编码,让业务不满足的情况下,可以自行修改 我们把解决方案取名Asp.netCoreT ...

  9. 动手写一个Remoting接口测试工具(附源码下载)

    基于.NET开发分布式系统,经常用到Remoting技术.在测试驱动开发流行的今天,如果针对分布式系统中的每个Remoting接口的每个方法都要写详细的测试脚本,无疑非常浪费时间.所以,我想写一个能自 ...

随机推荐

  1. Python之爬虫有感(一)

    urllib.request.Request('URL',headers = headers)User-Agent 是爬虫和反爬虫斗争的第一步,发送请求必须带User—Agent使用流程:    1. ...

  2. 【题解】P1396 营救-C++

    原题传送门 这道题目基本就是一个克鲁斯卡尔最小生成树的模板题,唯一不同的是,这道题目的最终目标不是所有点相连,而是只要s和t相连就可以了.还有就是这道题目求的是最小生成树中的最大边权值.但是,克鲁斯卡 ...

  3. UVA10375 选择与除法 Choose and divide 题解

    题目链接: https://www.luogu.org/problemnew/show/UVA10375 分析: 这道题可以用唯一分解定理来做. 什么是唯一分解定理?百度即可,这里也简介一下. 对于任 ...

  4. 使用SVG symbols建立图标系统完整指南

    从最开始的使用img图片,到后来的使用css sprite来减少服务器请求,再到流行的图形字体化图标Iconfont.现在,一种全新的图标使用方式开始流行了起来--SVG symbols图标. 工作原 ...

  5. 个人永久性免费-Excel催化剂功能第32波-空行空列批量插入和删除

    批量操作永远是效率提升的王道,也是Excel用户们最喜欢能够实现的操作虽说有些批量操作不一定合适Excel的最佳实践操作,但万千世界,无奇不有,特别是在国人眼中领导最大的等级森严的职场环境下.Exce ...

  6. win10 安装mysql(图文详情)避免卡在最后一步

    win10 安装mysql 本人安装mysql一般都是在镜像文件夹中下载(http://mirrors.sohu.com/),最近系统重做了之后安装mysql5.5.msi,可以安装成功.但是在最后的 ...

  7. android 发送邮件--实现 send email for android

    Android 发送邮件消息 用途:发送验证码,通过邮箱找回密码 不需要调用客户端直接使用代码进行发送 本项目使用到的jar包–本文结尾会附带下载链接 activation.jar additionn ...

  8. [小米OJ] 4. 最长连续数列

    思路: 时间限制为O(n),即不能使用先排序后寻找的方法. 这里利用哈希表查询插入复杂度都为O(1)的特性来解,利用一个哈希表来保存每一个数字以及其所在数列的长度. 遍历每一个数字n:查询表中是否存在 ...

  9. Linux C 网络编程——多线程的聊天室实现(服务器端)

    服务器端的主要功能: 实现多用户群体聊天功能(此程序最多设定为10人,可进行更改),每个人所发送的消息其他用户均可以收到.用户可以随意的加入或退出(推出以字符串"bye"实现),服 ...

  10. 使用.net core基于Razor Pages开发网站一些工作笔记

    本文是在实践工作中遇到的一些问题记录,并给出是如何解决的,.net core已经升级到3.0版本了,其实在项目中很早就已经在使用.net core来开发后台接口了,正好有个网站项目,就使用了Razor ...