日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > java >内容正文

java

从零写一个编译器(十一):代码生成之Java字节码基础

發布時間:2023/12/20 java 25 豆豆
生活随笔 收集整理的這篇文章主要介紹了 从零写一个编译器(十一):代码生成之Java字节码基础 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

項目的完整代碼在 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;)Vgetstatic java/lang/System/out Ljava/io/PrintStream;ldc "Hello World!"invokevirtual java/io/PrintStream/println(Ljava/lang/String;)Vreturn .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>()Vaload 0invokespecial java/lang/Object/<init>()Vaload 0sipush 0putfield MyClass/c Caload 0sipush 0putfield MyClass/a Ireturn .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 Iaload 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這個項目!

    轉載于:https://www.cnblogs.com/secoding/p/11384619.html

    總結

    以上是生活随笔為你收集整理的从零写一个编译器(十一):代码生成之Java字节码基础的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。