首页 > 编程语言 >java开发C语言编译器:JVM 的基本操作指令介绍及其程序运行原理

java开发C语言编译器:JVM 的基本操作指令介绍及其程序运行原理

时间:2023-06-14 11:04:16浏览次数:71  
标签:Binary java 程序运行 18 虚拟机 编译器 指令 堆栈 基本操作


更详细的讲解和代码调试演示过程,请参看视频
用java开发C语言编译器

更详细的讲解和代码调试演示过程,请参看视频
如何进入google,算法面试技能全面提升指南

如果你对机器学习感兴趣,请参看一下链接:
机器学习:神经网络导论

更详细的讲解和代码调试演示过程,请参看视频
Linux kernel Hacker, 从零构建自己的内核

java虚拟机是对实体计算机的模拟,任何用高级语言开发的程序,必须编译成二进制代码后,才能在CPU上运行,而这些二进制代码本质上是一系列基础指令集的集合,这些指令的作用非常简单,例如把数值存到某块内存,或者把信息从某地读出,然后放入到寄存器中。

同理,运行在java虚拟机上的字节码,其本质也是一系列作用单一的指令的集合。我们看看,要完成一个简单的计算功能,例如计算 6 * 9 的结果,虚拟机是如何依赖基础指令来完成相应计算功能的。计算式 6 * 9 被编译成java字节码后,对应的指令如下:

bipush  6
bipush  9
imul

我们前面强调过,jvm是以栈为基础来运行指令的,任何指令的执行,如果需要参数的话,这些参数必须先压入虚拟机的堆栈上,指令才可以正常执行。从上面的代码可以看成,指令imul 的作用是做乘法操作,要相乘的两个数在执行这句指令前,必须都压入到堆栈的顶部,因此指令bipush 6的作用是把数值6压入到堆栈顶部,bipush 9作用是把数值9压入到堆栈顶部,执行这两条指令后堆栈情况如下:
stack: 9, 6

当虚拟机执行指令imul后,会把堆栈顶部的两个数值弹出,把他们相乘,然后把乘积压入堆栈顶部,所以imul指令执行后,堆栈情况如下:
stack: 54

java虚拟机的指令有一些特点,在指令的前面会有一些字符用于表示指令所要操作的数据类型,例如bipush指令,push前面的字符i表示指令只接受整形数据,i的意思是int, 最前面的b表示byte, 也就是说bipush指令只能操作范围在-128到128之间的整数,同理如果指令是sipush,表示该指令只能把short类型的整数压入到堆栈。同理指令imul表示它只能处理两个整形数相乘,如果此时堆栈顶部的两个数字不是整形的话,那么执行imul指令时,虚拟机就会报错。以下我们给出字符对应的数据类型:

Letter

Type

a

reference 指令处理的是对象引用

b

byte or boolean

c

char

d

double

i

int

l

long

s

short

如果你看到指令fmul, 你就能知道,这条指令的作用是对两个浮点数做乘法。相应指令的具体作用,大家可以查询java虚拟机规范,这里我们不深入探讨。

我们也提到过,java虚拟机的运行环境,不单单有堆栈,它还有一个局部变量队列,用来存储临时变量的信息。因此java虚拟机也提供了相应的操作指令。假设虚拟机堆栈和局部变量队列的数据存储情况如下:

stack: 1
array: _ _ _ 2 _ _

也就是堆栈顶部数据为1,在具备变量队列中,下标为3的地方存储了一个整形2,如果执行了指令 istore 3, 那么虚拟机就会把局部变量队列下标为3处所存储的整形数压入堆栈顶部,于是执行完指令后,堆栈和队列的情况如下:

stack: 2, 1
array: _ _ _ _ _ _

如果接着执行的指令是 iload 2, 它的意思是,把堆栈顶部的数值弹出,然后存储到局部变量队列下标为2的区域处,于是执行完后,虚拟机的堆栈和队列情况如下:

stack: 1
array: _ _ 2 _ _ _

同理load和store前面的字符用于表明指令所能处理的数据,如果想把一个浮点数从队列压入到堆栈,那么对应的指令就是 fstroe . 假设局部变量队列在下标2为1 的地方存储的是对一个对象的引用,要想把这个引用压入到堆栈顶部,对应的指令就是 astore 1. store 前面的a表示的就是对象引用。这里我们只需要理解指令的基本原理,具体的相关指令,大家可以通过查询java虚拟机规范来了解。

有了上面的基础知识后,我们就可以看看,如何将下面的C语言代码编译成java字节码:

void f() {
   printf("The result of 3 multiply 6 is :%d", 3*6);
}

void main() {
   f();
}

我们的编译器在解析上面代码时,读取到数字字符是,会根据表达式:
Unary -> NUMBER
进行解析,因此当编译器在执行上面表达式的解析时,我们就知道当前已经读取到了数字常量,此时我们可以通过push指令把该数字常量压入虚拟机堆栈上。解析上面表达式的代码是在UnaryExecutor中,所以在UnaryExecutor.java中,我们做如下修改:

public class UnaryNodeExecutor extends BaseExecutor implements IExecutorReceiver{
....
  public Object Execute(ICodeNode root) {
    ....
    case CGrammarInitializer.Number_TO_Unary:
            text = (String)root.getAttribute(ICodeKey.TEXT);
            boolean isFloat = text.indexOf('.') != -1;
            if (isFloat) {
                value = Float.valueOf(text);
                root.setAttribute(ICodeKey.VALUE, Float.valueOf(text)); 
            } else {
                value = Integer.valueOf(text);
                root.setAttribute(ICodeKey.VALUE, Integer.valueOf(text));
            }
            ProgramGenerator.getInstance().emit(Instruction.IPUSH, value.toString());
            break;
    ....
  }
....

在编译器读入代码,解析到一个数字常量时,我们输出push 指令,后面跟着对应的数字常量,所以当我们的编译器解析代码时,读取到数字字符3,那么上面的代码被执行,于是语句:
ProgramGenerator.getInstance().emit(Instruction.IPUSH, value.toString());
就会在java汇编文件中输出一条语句:
sipush 3

同理,当读取字符6时,上面的代码又会被执行,于是在java汇编文件中又再输出一条语句:
sipush 6

当表达式3 * 6 被解析时,由于该算式对应的语法表达式是:
Binary -> Binary * Binary

所以,当我们的编译器执行上面的语法表达式时,我们就可以输出乘法指令imul. 执行上面表达式的解析是在BinaryExecutor.java中,所以代码改动如下:

public class BinaryExecutor extends BaseExecutor{
    @Override
    public Object Execute(ICodeNode root) {
    ....
    case CGrammarInitializer.Binary_Plus_Binary_TO_Binary:
        case CGrammarInitializer.Binary_DivOp_Binary_TO_Binary:
        case CGrammarInitializer.Binary_Minus_Binary_TO_Binary:
        case CGrammarInitializer.Binary_Start_Binary_TO_Binary:

        ......

         else if (production ==  CGrammarInitializer.Binary_Start_Binary_TO_Binary) {
                String text = root.getChildren().get(0).getAttribute(ICodeKey.TEXT) + " * " + root.getChildren().get(1).getAttribute(ICodeKey.TEXT);
                root.setAttribute(ICodeKey.VALUE, val1 * val2); 
                root.setAttribute(ICodeKey.TEXT,  text);
                System.out.println(text + " is " + (val1 * val2) );

                ProgramGenerator.getInstance().emit(Instruction.IMUL);
            }
            else {
            ....
            ....
            }

            break;
    ....
    }

大家可以看到,在解析两数相乘时,编译器通过代码:
ProgramGenerator.getInstance().emit(Instruction.IMUL);
在java汇编文件中输出指令 imul,到此我们编译器成功输出了以下语句:

sipush  3
sipush  6
imul

上面三条指令执行后,虚拟机的堆栈顶部存有的数值是18.接下来我们需要把数值18打印出来。通过上节我们了解到,要把信息输出到控制台上时,需要调用对象System.out 的 print 函数,也就是需要把System.out对象先压到堆栈上,再把要输出的内容压到堆栈上,也就是所,如果我们想要把结果18打印到控制台上,那么堆栈上的内存必须是这样:

stack: 18, System.out

但现在问题是,数值18处于堆栈顶部,要想实现上面所需的堆栈数据排列,我们需要把数值18先拿开,然后把对象System.out压入堆栈,接着再把数值18重新压入堆栈,因此我们需要先使用istore指令,把数值18从堆栈放入局部变量队列,也就是先执行指令istore 0, 使得虚拟机堆栈和队列的数据存储情况如下:

stack:
array: 18

然后 执行指令getstatic ,把 System.out对象压入堆栈:

stack: System.out
array: 18

最后使用指令 iload 0 , 把位于队列偏移为0处的数值18重新加载会堆栈,执行完指令iload 0 后,虚拟机堆栈和队列上的内容如下:
stack: 18, System.out
array:

此时要输出到控制台的内容和执行输出操作的System.out对象都已经在堆栈顶部,我们再通过指令invokevirtual 调用System.out对象的Print接口就可以实现把数值18输出到控制台上的目的了。

因此编译器在把语句 printf(“The result of 3 multiply 6 is :%d”, 3*6); 转换成java字节码时,分以下几步走,首先把字符串”The result of 3 multiply 6 is ” 输出到控制台,接着解析表达式 3*6,生成上头所说的三条java指令,然后再执行上头所说的步骤,把数值18和类对象System.out放入到堆栈顶部,进而再把数值18输出到控制台上。

这里的逻辑有点繁琐,请大家通过视频获得更清楚的讲解和演示。

上头所说的操作都需要在编译器解析printf调用时去具体处理,因此相关代码的实现在ClibCall.java 中,代码如下:

private Object handlePrintfCall() {
        ArrayList<Object> argsList = FunctionArgumentList.getFunctionArgumentList().getFuncArgList(false);
        String argStr = (String)argsList.get(0);
        String formatStr = "";

        int i = 0;
        int argCount = 1;
        while (i < argStr.length()) {
            if (argStr.charAt(i) == '%' && i+1 < argStr.length() && 
                    argStr.charAt(i+1) == 'd') {
                i += 2;
                generateJavaAssemblyForPrintf(formatStr);
                formatStr += argsList.get(argCount);
                argCount++;
                printInteger();
            } else {
                formatStr += argStr.charAt(i);
                i++;
            }
        }

        System.out.println(formatStr);

        generateJavaAssemblyForPrintf("\n");
        return null;
    }

    private void generateJavaAssemblyForPrintf(String s) {
        ProgramGenerator generator = ProgramGenerator.getInstance();
        generator.emit(Instruction.GETSTATIC, "java/lang/System/out Ljava/io/PrintStream;");
        generator.emit(Instruction.LDC, "\"" + s + "\"");
        String printMethod = "java/io/PrintStream/print(Ljava/lang/String;)V";
        generator.emit(Instruction.INVOKEVIRTUAL, printMethod);
    }

    private void printInteger() {
        ProgramGenerator generator = ProgramGenerator.getInstance();
        generator.emit(Instruction.ISTORE, "0");
        generator.emit(Instruction.GETSTATIC, "java/lang/System/out Ljava/io/PrintStream;");
        generator.emit(Instruction.ILOAD, "0");
        String printMethod = "java/io/PrintStream/print(I)V";
        generator.emit(Instruction.INVOKEVIRTUAL, printMethod);
    }

在handlePrintfCall调用中,在输出数值之前,先通过generateJavaAssemblyForPrintf调用,把字符串先输出,然后调用printInteger接着把数值输出到控制台上,在printInteger中,先把指令istore 0 输出到Java汇编文件里,然后输出指令getstatic , 目的就是把System.out对象压入到堆栈上,然后再次通过输出指令 iload 0 , 把数值18重新压入到堆栈顶部,最后在通过invokevirtual指令,把数值18输出到控制台。

上面的代码执行后,C语言代码被编译成如下java汇编代码:

.class public CSourceToJava
.super java/lang/Object

.method public static main([Ljava/lang/String;)V
    invokestatic    CSourceToJava/f()V
    return
.end method
.method public static f()V
    sipush  3
    sipush  6
    imul
    getstatic   java/lang/System/out Ljava/io/PrintStream;
    ldc "The result of 3 multiply 6 is :"
    invokevirtual   java/io/PrintStream/print(Ljava/lang/String;)V
    istore  0
    getstatic   java/lang/System/out Ljava/io/PrintStream;
    iload   0
    invokevirtual   java/io/PrintStream/print(I)V
    return
.end method

.end class

我们可以看到,代码先是通过三条指令
sipush 3
sipush 6
imul
计算 3*6 , 接着把字符串”The result of 3 multiply 6 is :”压到堆栈打印出来,最后再通过前面所说的指令操作,把堆栈顶部的数值18打印到控制台上。

请大家通过视频获得更详实的讲解和更清晰的调试演示过程。

更多技术信息,包括操作系统,编译器,面试算法,机器学习,人工智能,请关照我的公众号:

java开发C语言编译器:JVM 的基本操作指令介绍及其程序运行原理_堆栈


标签:Binary,java,程序运行,18,虚拟机,编译器,指令,堆栈,基本操作
From: https://blog.51cto.com/u_16160261/6476229

相关文章

  • 编译原理动手实操,用java实现一个简易编译器-语法解析
    语法和解析树:举个例子看看,语法解析的过程。句子:“我看到刘德华唱歌”。在计算机里,怎么用程序解析它呢。从语法上看,句子的组成是由主语,动词,和谓语从句组成,主语是“我”,动词是“看见”,谓语从句是”刘德华唱歌“。因此一个句子可以分解成主语+动词+谓语从句:句子-->主语+动词+谓语......
  • java开发C语言编译器: return 语句的解释和执行
    在C语言程序中,很多函数并不是执行全部语句后,才从最底部返回的,大多数情况下,当某些条件成立时就可以通过return语句立即返回,而无需执行接下来的代码,本节,我们继续增强java开发的C语言解释器功能,使其能够处理return语句,完成本节代码后,我们的C语言解释器能够正常解析和执行下面的代码:in......
  • java实现C语言编译器:实现有参数的函数调用
    上一节,我们实现了没有参数传递的函数调用,本节,我们看看如何实现有参数传递的函数调用。有参数的函数调用要比无参数的函数调用复杂的多,一个难题在于,我们需要确定参数变量的作用域,例如下面的代码:inta;voidf(inta,intb){intc;c=a+b;}在代码里,有两个同名变量都......
  • iOS程序运行生命周期
    iOS程序运行生命周期  在文件AppDelegate.m中定义了iOS程序运行的各个生命周期,清楚了解程序运行中的每个生命周期,对于我们的开发工作将提供极大的帮助。下面代码就是AppDelegate.m中各生命周期对应的方法。 -(BOOL)application:(UIApplication*)applicationdidFinishLaunch......
  • Linux系统下如果在编译时指定程序运行时动态链接库相对目录,及为程序收集默认不安装动
    1、在编译中指定编译的程序去查找的动态链接库目录qmake是这样指定运行时动态库目录的:  QMAKE_LFLAGS+=-Wl,-rpath=/usr/local/ultrasec/policyfilter/lib/ 2、用lddPROG  查看程序的动态链接库,如果没有的动态链接库,就从其它计算机或者网络上找这个库,然后拷贝到指......
  • Latex编译器推荐(面向初学者或者懒得折腾的朋友,主要针对windows用户)
    原文链接:https://blog.sciencenet.cn/blog-478347-1215384.html大家平时用的最多的排版工具想必就是Microsort的Word或者WPS了,所见即所得,Latex是另外一种排版工具,需要编译才可以生成pdf。相信大家在投稿的时候,会发现很多杂志都提供的textemplate。至于Latex好还是word好,这个已......
  • 2023.6.12 03.数据库基本操作
    1.数据库连接⽅式2.SQL语⾔3.Mysql数据库对应与应⽤4.数据库基本操作5.数据库增删查改6.数据库查询语句6.1单表查询6.2多表查询6.3⼦查询 系统数据库information_schema(虚拟库)⽤户表信息、列信息、权限信息、字符信息等#查询有多少个库mysql>SELECTCOUNT(*)......
  • 简单编译器
    目录0x01背景0x02SML语法0x03应用源码:编译过程:sml.txt文件:执行0x04SML_C实现sml_compiler.hsml_compiler.c0x05总结0x01背景《C语言大学教程-第八版》(《CHowtoProgram》)391页,第十二章作业,专题:创建自己的编译器在练习题7.27至练习题7.29中,我们介绍了Simpletron机器......
  • 当GaussDB遇上了毕昇编译器
    摘要:当应用软件及硬件确定后,编译器对应用的自动优化将成为应用性能的关键。从应用优化说起一个应用的优化通常有架构级优化、模块级优化和函数级优化,高性能作为云数据库GaussDB主打特性之一,其在这几方面都进行了大量的优化,也有很强的性能表现。如何进一步提升性能,是否还有其他方......
  • VC6.0编译器参数设置
    VC6.0编译器参数的设置主要通过VC的菜单项Project->Settings->C/C++页来完成。我们可以看到这一页的最下面ProjectOptions中的内容,一般如下:/nologo/MDd/W3/Gm/GX/ZI/Od/D"WIN32"/D"_DEBUG"/D"_WINDOWS"/D"_AFXDLL"/D"_MBCS"/Fp"Debug......