精华内容
下载资源
问答
  • java开发C语言编译器结构体是C语言中,最为复杂的原生数据结构,它把多种原生结构结合在一起,形成一个有特点含义的数据结构,要实现一个完整的C语言编译器或解释器,就必须要拥有对结构体的解...


    用java开发C语言编译器

    结构体是C语言中,最为复杂的原生数据结构,它把多种原生结构结合在一起,形成一个有特点含义的数据结构,要实现一个完整的C语言编译器或解释器,就必须要拥有对结构体的解析能力,本节,我们在当前解释器的基础上,增加结构体的解释执行能力,完成本节后,我们的解释器可以解析执行下面代码:

    void main() {
    
    struct TAG {
    int v1;
    int v2;
    char v3;
    } tag;
    
    struct TAG  myTag;
    struct TAG  herTag;
    myTag.v1 = 1;
    herTag.v1 = 2;
    
    printf("set filed v1 of struct myTag to value : %d, and v1 of herTag to value : %d", myTag.v1, herTag.v1); 
    
    }
    

    我们先回忆一下结构体的语法表达式:

    struct_specifier -> STRUCT OPT_TAG LC def_list RC
                        | STRUCT tag;
    

    我们对比下具体的结构体定义和语法表达式的对应关系:

    struct TAG {
    int v1;
    int v2;
    char v3;
    } tag;
    

    上面定义中struct 是关键字,对应语法表达式中的STRUCT终结符,TAG 是结构体定义名,对应表达式中的OPT_TAG; int v1;int v2; char v3; 这三个变量定义对应于def_list,.

    另一个与结构体相关的语法表达式是:

    unary -> unary STRUCTOP NAME
    

    上面表达式用来说明对结构体某个值域的引用,例如语句myTag.v1就可以对应上面的语句,STRUCTOP是终结符,他对应的文本字符为”.”, 或 “->”.

    在前面的课程我们详细说明过,当解释器解析到结构体的定义时,它先给结构体变量构建一个symbol对象,该symbol对象的修饰符,也就是specifier含有一个结构体叫StructDefine, StructDefine 会为结构体中的每一个变量创建一个Symbol对象,然后把这些对象串联成一个队列,仍然以上面的结构体定义为例,我们的解释器解析后,形成如下结构:
    (图一)

    当我们定义一个结构体变量时,例如语句struct TAG myTag; 任何有关变量声明的语句经过一系列递归后,最后对应的语法表达式为:

    def -> specifiers decl_list SEMI;
    

    当解释器解析代码是,递归到上面的表达式时,解释器要判断一下,当前声明的变量是否是结构体,如果是的话,那么必须为当前结构体变量赋值一份结构体内部的变量所对应的Symbol队列,也就是说,当解释器解析到语句 struct TAG myTag;时,会把上图的结构再复制一份:
    (图二)

    这样一来,对结构体某个变量的值域的读写,直接转换成对某个变量Symbol的读写就可以了,例如代码中的语句:

    myTag.v1 = 1;

    这相当与把数值1写入到上图中最下面v1所对应的Symbol对象即可。

    我们看看相应的代码实现,第一步就是,当解析到结构体的变量声明时,把结构体定义的符号表数据结构复制一份,也就是从图1到图2的过程:

    public class LRStateTableParser {
    ....
        private void takeActionForReduce(int productNum) {
    
            switch(productNum) {
            ....
            case CGrammarInitializer.Specifiers_DeclList_Semi_TO_Def:
                Symbol symbol = (Symbol)attributeForParentNode;
                Typelink specifier = (Typelink)(valueStack.get(valueStack.size() - 3));
                typeSystem.addSpecifierToDeclaration(specifier, symbol);
                typeSystem.addSymbolsToTable(symbol, symbolScope);
    
                handleStructVariable(symbol);
                break;
            ....
            }
    ....
    }
    
    private void handleStructVariable(Symbol symbol) {
           //先看看变量是否属于struct类型
           boolean isStruct = false;
           Typelink typelink = symbol.typelinkBegin;
           Specifier specifier = null;
           while (typelink != null) {
               if (typelink.isDeclarator == false) { 
                   specifier = (Specifier)typelink.getTypeobject();
                   if (specifier.getType() == Specifier.STRUCTURE) {
                       isStruct = true;
                       break;
                   }
               }
    
               typelink = typelink.toNext();
           }
    
           if (isStruct == true) {
               //把结构体定义中的每个变量拷贝一份,存储到当前的symbol中
               StructDefine structDefine = specifier.getStructObj();
               Symbol copy = null, headCopy = null, original = structDefine.getFields();
               while (original != null) {
                   if (copy != null) {
                      Symbol sym = original.copy();
                      copy.setNextSymbol(sym);
                      copy = sym;
                   } else {
                       copy = original.copy();
                       headCopy = copy;
                   }
    
                   original = original.getNextSymbol();
               }
    
               symbol.setArgList(headCopy);
           }
       }
    

    handleStructVariable 这个函数的作用就是把图一中的结构复制一遍,实现从图一到图二的转换。这样一来,当声明同一个结构体类型的不同变量时,就像我们的示例代码中,声明了两个结构体变量,分别是myTag,herTag, 那么对应v1的Symbol对象就有两份,对不同的v1赋值,实际上是把数值赋值到不同的Symbol对象中。

    我们再看看对结构体变量的读写,例如语句:
    myTag.v1 = 1;

    当执行上面语句时,解释器先获得要读写的结构体变量对应域的名称,上面给定代码,要赋值的域的名称是”v1”, 然后在符号表中,找到变量名myTag对应的Symbol对象,然后找到Specifer,进而找到StructDefine对象,在该对象中,找到结构体里面各个变量所对应的Symbol队列,然后利用域的名称字符串“v1”,在队列中找到独有的Symbol对象,最后把数值1写入到该Symbol对象中。

    相应代码如下:

    public class UnaryNodeExecutor extends baseExecutor{
    
        @Override
        public object Execute(ICodeNode root) {
        ....
         case CGrammarInitializer.Unary_StructOP_Name_TO_Unary:
                child = root.getChildren().get(0);
                String fieldName = (String)root.getAttribute(ICodeKey.TEXT);
                symbol = (Symbol)child.getAttribute(ICodeKey.SYMBOL);
    
                Symbol args = symbol.getArgList();
                while (args != null) {
                    if (args.getName().equals(fieldName)) {
                        break;
                    }
    
                    args = args.getNextSymbol();
                }
    
                if (args == null) {
                    System.err.println("access a filed not in struct object!");
                    System.exit(1);
                }
    
                root.setAttribute(ICodeKey.SYMBOL, args);
                root.setAttribute(ICodeKey.VALUE, args.getValue());
                break;
        ....
        }
    }
    

    如果通过结构体对应成员的名字字符串,在StructDefine中的Symbol队列中找不到给定名字的Symbol对象,这表明程序要访问结构体定义中不存在的变量,从而我们的程序就会因此种异常而退出。

    声明:

    本文于网络整理,版权归原作者所有,如来源信息有误或侵犯权益,请联系我们删除或授权事宜。

    展开全文
  • 我们把C代码编译成java字节码,这样我们的C语言便具备了可跨品台属性。通过把C语言编译成java字节码,我们不但能够继续学习和掌握编译原理相关的算法技术,于此同时,还能深入理解java虚拟机的基本原理,此乃...

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

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

    我们已经完成了C语言解释器的基本开发,当下解释器已经能解释执行大部分C语言程序了,接下来我们进入C语言编译器的开发工作。

    我们把C代码编译成java字节码,这样我们的C语言便具备了可跨品台属性。通过把C语言编译成java字节码,我们不但能够继续学习和掌握编译原理相关的算法技术,于此同时,还能深入理解java虚拟机的基本原理,此乃一举两得之妙。完成本节内容后,我们的编译器能把下面的C源程序编译成java 汇编代码:

    void main() {
        printf("Hello World!");
    }

    上面代码编译后形成下面内容(CSourceToJava.j):

    .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

    上面代码是基于java标准的java汇编代码,我们编译器的目标就是把C源程序转义成java汇编,最后再通过java汇编编译器将汇编代码转换成16进制的字节码。

    市面上有不少java汇编编译器,最常用的是Jasmine,我们这里使用的是一个国外大牛开发的编译器叫oolong, 该编译器是一个jar包,当我们的编译器生成上面的java汇编代码后,通过以下命令把汇编代码转换成java字节码:

    java -cp oolong.jar COM.sootNsmoke.oolong.Oolong CSourceToJava.j

    运行上面程序后,在本地目录下会生成一个名为CSourceToJava.class的十六进制文件,用java命令可以直接运行该文件内容:
    java CSourceToJava
    运行后结果为:
    Hello World!

    实际上,上面的java汇编代码等价于下面的java源程序:

    public class CSourceToJava {
        public static void main(String[]) {
            System.out.println("Hello World");
        }
    }

    大家不难把java汇编代码中的指令和源程序中的代码对应起来,例如:
    .class public CSourceToJava 对应于public class CSourceToJava
    .method public static main 对应于public static void main(String[])
    等等。

    要理解java汇编语言及其逻辑结构,我们需要掌握java虚拟机的指令集以及虚拟机的运行机制,这些都是我们后面要非常深入的,耐心的,细致的去研究和学习的内容,在这里我们先一掠而过,我们先通过动手实践,获取第一手的感性认识,为后面的学习打下基础。

    我们看看如何实现java汇编程序的生成,首先我们新增一个CodeGenerator类,代码如下:

    package backend.Compiler;
    import java.io.File;
    import java.io.FileNotFoundException;
    import java.io.PrintStream;
    import java.io.PrintWriter;
    
    public class CodeGenerator {
        private static PrintWriter assemblyFile;
        private static int instructionCount = 0;
    
        protected static String programName = "CSourceToJava";
    
    
        public CodeGenerator() {
    
            String assemblyFileName = programName + ".j";
    
            try {
                assemblyFile = new PrintWriter(new PrintStream(new 
                        File(assemblyFileName)));
            } catch (FileNotFoundException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    
    
        public void emitDirective(Directive directive) {
            assemblyFile.println(directive.toString());
            assemblyFile.flush();
            ++instructionCount;
        }
    
        public void emitDirective(Directive directive, String operand) {
            assemblyFile.println(directive.toString() + " " + operand);
            assemblyFile.flush();
            ++instructionCount;
        }
    
        public void emitDirective(Directive directive, int operand) {
            assemblyFile.println(directive.toString() + " " + operand);
            ++instructionCount;
        }
    
        public void emitDirective(Directive directive, String operand1, String operand2) {
            assemblyFile.println(directive.toString() + " " + operand1 + " " + operand2);
            ++instructionCount;
        }
    
        public void emitDirective(Directive directive, String operand1, String operand2, String operand3) {
            assemblyFile.println(directive.toString() + " " + operand1 + " " + operand2 + " " + operand3);
            ++instructionCount;
        }
    
        public void emit(Instruction opcode) {
            assemblyFile.println("\t" + opcode.toString());
            assemblyFile.flush();
            ++instructionCount;
        }
    
        public void emit(Instruction opcode, String operand) {
            assemblyFile.println("\t" + opcode.toString() + "\t" + operand);
            assemblyFile.flush();
            ++instructionCount;
        }
    
        public void emitBlankLine() {
            assemblyFile.println();
            assemblyFile.flush();
        }
    
        public void finish() {
            assemblyFile.close();
        }
    
    
    }
    

    该类先创建一个以.j结尾的文件,然后通过PrintWriter 把内容写入到该文件中,在该类中有很多以emit开头的函数,这些函数的作用就是把相关的java汇编指令写入文件。

    在java汇编代码中,以符号”.”开头的,我们称之为宏指令,例如:
    .class public CSourceToJava
    .super java/lang/Object

    java汇编代码中有一类专门指令叫操作符,例如:
    getstatic
    ldc
    等等。

    以上这些指令的作用我们会在后续课程中详细研究,在此先掠过。于是根据宏指令,我们有对应的以下java代码:

    package backend.Compiler;
    
    public enum Directive {
        CLASS_PUBLIC(".class public"),
        END_CLASS(".end class"),
        SUPER(".super"),
        FIELD_PRIVATE_STATIC(".field private static"),
        METHOD_STATIC(".method static"),
        METHOD_PUBLIC(".method public"),
        METHOD_PUBBLIC_STATIC(".method public static"),
        END_METHOD(".end method"),
        LIMIT_LOCALS(".limit locals"),
        LIMIT_STACK(".limit stack"),
        VAR(".var"),
        LINE(".line");
    
        private String text;
    
        Directive(String text) {
            this.text = text;
        }
    
        public String toString() {
            return text;
        }
    }
    

    根据操作符指令,我们也有对应的java类:

    package backend.Compiler;
    
    public enum Instruction {
        LDC("ldc"),
    
        GETSTATIC("getstatic"),
    
        INVOKEVIRTUAL("invokevirtual"),
        RETURN("return");
    
        private String text;
        Instruction(String s) {
            this.text = s;
        }
    
        public String toString() {
            return text;
        }
    }
    

    有了上面的基础后,我们就可以把java汇编代码的基础部分编译出来,我们构造一个 ProgramGenerator 类,它继承与CodeGenerator:

    package backend.Compiler;
    
    public class ProgramGenerator extends CodeGenerator {
        private static ProgramGenerator instance = null;
    
        public static ProgramGenerator getInstance() {
            if (instance == null) {
                instance = new ProgramGenerator();
            }
    
            return instance;
        }
    
        private ProgramGenerator()  {
    
        }
    
        public void generate() {
            emitDirective(Directive.CLASS_PUBLIC, programName);
            emitDirective(Directive.SUPER, "java/lang/Object");
            generateMainMethod();
        }
    
        private void generateMainMethod() {
            emitBlankLine();
            emitDirective(Directive.METHOD_PUBBLIC_STATIC, "main([Ljava/lang/String;)V");
    
        }
    
        public void finish() {
            emitDirective(Directive.END_METHOD);
            emitDirective(Directive.END_CLASS);
    
            super.finish();
        }
    }
    

    上面的ProgramGenerator可以生成java汇编源码中的以下部分:

    .class public CSourceToJava
    .super java/lang/Object
    
    .method public static main([Ljava/lang/String;)V
    
    .end method
    .end class

    接下来我们要生成打印字符串的部分,这部分工作在解析printf(“Hello World!”);这条语句时可以生成。在前面的课程中,我们已经详细解析如何实现printf库函数调用,它的实现是在ClibCall.java 中,我们就在对该库函数的解析中,生成对应的java汇编代码,因此实现代码如下,在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;
                    formatStr += argsList.get(argCount);
                    argCount++;
                } else {
                    formatStr += argStr.charAt(i);
                    i++;
                }
            }
    
            System.out.println(formatStr);
    
            generateJavaAssemblyForPrintf(formatStr);
    
            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/println(Ljava/lang/String;)V";
            generator.emit(Instruction.INVOKEVIRTUAL, printMethod);
            generator.emit(Instruction.RETURN);
    
        }

    generateJavaAssemblyForPrintf 这个调用就用于生成输出字符串的java汇编源码,上面这段代码运行后,生成的java汇编代码为:

        getstatic   java/lang/System/out Ljava/io/PrintStream;
        ldc "Hello World!"
        invokevirtual   java/io/PrintStream/println(Ljava/lang/String;)V
        return

    结论:
    本节,我们初步接触了java汇编语言,了解到一些有关java虚拟机的宏指令和操作符指令,并通过一个实例,把C语言编写的代码转换成了java汇编代码,最后再通过汇编编译器把生成的java汇编转换成虚拟机可以加载执行的十六进制java字节码,通过实践,先让大家获得第一手感性认识,在后面的课程中,我们将深入研究java虚拟机的运行机制,并不断完善我们编译器的实现,最终能让我们的编译器能够正确的将C源程序编译成可运行的java字节码。

    更详细的讲解和代码调试演示请参看视频。

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

    展开全文
  • 把C语言中的struct结构体编译成java中的class

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

    C语言是一种面向过程的语言,由于不像java那样具备面向对象的特性,所以在C语言中不存在类这样的对象,但C语言中的struct结构体跟java的类具有很多相通之处,struct本质上等价于一个没有方法只有数据,并且数据属性全是public的类。

    本节我们要实现的目标是将包含struct定义的C源程序编译成java字节码,我们将会把struct编译成对应的java类,当完成本节代码后,我们的编译器能将下面C代码编译成java字节码并在jvm上正确运行:

    struct CTag {
        int x;
        char c;
    };
    
    void main() {
       struct CTag myTag;
       myTag.x = 1;
       printf("value of x in myTag is %d", myTag.x);
    }

    我们先了解jvm用于创建和操作类对象的相关指令。当虚拟机创建一个具体类的实例之时,它需要指令new, 假设有个类,其名为ClassName,那么在虚拟机上创建一个它的实例对应的指令就是:

    new ClassName

    执行上面语句后,在虚拟机的堆栈顶部就会有一个对象实例,但代码还不能直接使用这个实例,该实例的使用必须要先初始化。我们知道,每个类必然都有自己的构造函数,例如下面这个类:

    public ClassName {
        public ClassName(){}
        public ClassName(String name){}
    }

    该类有两个构造函数,一个不带参数,一个带有一个String类型的参数。在初始化一个该类的实例时,这两个构造函数中,必有一个会被调用。从代码上看,每个类的构造函数都是跟类的名字是一样的,但在虚拟机内部,所有类的构造函数名一律转换为init,所以上面类的构造函数在虚拟机内部是这样的:

    <init>() V
    <init>(Ljava/lang/Strin;)V

    第一个init对应的是类定义里不带参数的构造函数,第二个init对应的是带String类型参数的构造函数。假设虚拟机通过new 指令在堆栈上构建了一个ClassName的实例对象,那么接下来它要调用不带输入参数的构造函数来初始化实例对象时,它会这么做:

    new ClassName
    dup
    invokespecial ClassName/<init>() V

    上面指令中, new ClassName现在堆栈顶部创建一个类的实例,执行后堆栈情况如下:
    stack:
    ClassName

    接着dup指令的作用是,把堆栈顶部的对象复制一份后再次压入栈顶,执行这条指令后,堆栈情况如下:
    stack:
    ClassName
    ClassName

    invokespecial 是调用指定某个类实例中成员函数的指令,如果我们想调用某个类的相关接口,那么需要把该类的实例压入堆栈顶部,然后执行指令invokespecial, 该指令后面跟着的是要调用的类的接口名称,它的格式如下:

    类名/接口名

    因为我们要调用ClassName实例对象的无参数构造函数,根据上面原理,虚拟机就需要使用invokespecial指令.指令执行后,压入堆栈的类实例就会从堆栈顶部移除,所以调用完构造函数后,堆栈顶部就只剩下一个类的实例.
    stack:
    ClassName

    接下来,我们看看java一个类的定义是如何在虚拟机里定义的,假设我们有一个类定义如下:

    public class CTag {
        public int x;
        public char c;
        public CTag() {
            this.x = 0;
            this.c = 0;
        }
    }

    这个类的定义很简单,它只含有两个公开成员变量,同时有一个不带输入参数的构造函数,那么上面代码转换成java汇编代码时,情况如下:
    public class CTag
    这句类声明会被转换成如下代码:

    .class public CTag
    .super java/lang/Object

    .class是java汇编语言的专有指令,它用来声明一个类,.super也是专有指令,用来表示一个类的父类,在java中,Object类是所以其他类的父类,所以上面代码转换成java汇编后会带有.super对应的语句,用来声明该类的父类。

    接下来就是对类的成员变量进行声明,声明类成员变量的指令是.field 于是两个公开类型的成员变量在java汇编中会变成如下形式:

    .field public c C
    .field public x I

    跟着就是要将构造函数转换成Java汇编了,我们前面讲解过,当某个函数被调用的时候,相关输入参数会存放到局部变量队列。当类的成员函数被调用时,有点特别,那就是类实例本身会被当做参数存放到局部变量队列的第0个位置,这其实就相当于this指针。

    完成了对成员变量的声明后,接下来就是构造函数的实现,首先是构造函数的接口声明:

    .method public <init>()V

    了解面向对象编程原理的话,我们就知道子类在初始化自己时,必须先调用父类的构造函数,所以当初始化构造函数init执行时,必须先执行父类构造函数,代码如下:

    aload   0
    invokespecial   java/lang/Object/<init>()V

    前面我们说过,当类的成员函数被调用时,类的实例对象会被存储在局部变量队列的第0个位置,所以指令aload 0 作用是把类的实例对象先压入栈顶,
    invokespecial java/lang/Object/() V
    的作用就是调用父类Object类的构造函数,完成这个步骤后,代码就要将两个成员变量赋初值为0.

    要想改变一个类成员变量的值,jvm需要执行三个步骤,首先是把类的实例加载到堆栈顶部,然后把要赋值的内容压入堆栈,最后使用putfield指令把数值存入类的成员变量,所以对于与代码this.c = 0; 它转换成java汇编后,代码如下:

    aload 0
    sipush 0
    putfield CTag/c  C

    同理可得,this.x = 0;这条语句对应的java汇编代码为:

    aload 0
    sipush 0
    putfield CTag/x  I

    上面代码中putfield指令最后的C和I对应的是成员变量的数据类型,x是整形,所以它对应I, c是字符,所以它对应的类型就是C.终上所述,整个构造函数的java汇编实现如下:

    .method public <init>()V
        aload   0
        invokespecial   java/lang/Object/<init>()V
        aload   0
        sipush  0
        putfield    CTag/c C
        aload   0
        sipush  0
        putfield    CTag/x I
        return
    .end method

    最后,整个类对应的java汇编代码如下:

    .class public CTag
    .super java/lang/Object
    .field public c C
    .field public x I
    .method public <init>()V
        aload   0
        invokespecial   java/lang/Object/<init>()V
        aload   0
        sipush  0
        putfield    CTag/c C
        aload   0
        sipush  0
        putfield    CTag/x I
        return
    .end method
    .end class

    到这里你可能就明白,当我们要把struct CTag转换成java字节码时,我们只要把CTag转换成对应的类,然后把它编译成上面的java汇编代码也就可以了。剩下的问题是,我们如何访问一个类的成员变量。在jvm中,访问一个类的成员变量,要分两步走,首先把类的实例压入堆栈,然后使用getfield指令将对应的类成员变量的值读入堆栈顶部。如果我们想要读取CTag.x的值,那么对应的java汇编代码如下:

    aload 0  ;假设CTag实例位于具备变量队列第0个位置
    putfield CTag/x  I

    执行上面语句后,CTag.x的值就会存储在堆栈顶部。有了这些理论知识后,我们就可以着手实现代码的编译了。

    当我们编译器在解析代码,遇到语句myTag.x 时,我们先看看myTag对应的结构体是否被编译成对应的java类,如果已经被编译过了,那么我们直接通过指令读取myTag.x的值,如果还没有被编译过,那么我们就生成对应的java类定义,由此,在ProgramGenerator.java中,添加如下代码:

    public class ProgramGenerator extends CodeGenerator {
    ....
    private ArrayList<String> structNameList = new ArrayList<String>();
    
    
    
        public void putStructToClassDeclaration(Symbol symbol) {
    
        private ArrayList<String> structNameList = new ArrayList<String>();
        public void putStructToClassDeclaration(Symbol symbol) {
            //判断传入的Symbol变量是否是结构体变量,不是的话立刻返回
            Specifier sp = symbol.getSpecifierByType(Specifier.STRUCTURE);
            if (sp == null) {
                return;
            }
    
            /*
             * 在队列structNameList中查询Symbol对应的结构体名字是否已经存储在队列中,如果在队列中有了
             * 那表明该结构体已经被转换成java类,并且类的定义已经转换成java汇编语言了
             */
            StructDefine struct = sp.getStructObj();
            if (structNameList.contains(struct.getTag())) {
                return;
            } else {
                structNameList.add(struct.getTag());
            }
    
            /*
             * 输出相应指令,把结构体转换成java类
             */
            this.emit(Instruction.NEW, struct.getTag());
            this.emit(Instruction.DUP);
            this.emit(Instruction.INVOKESPECIAL, struct.getTag()+"/"+"<init>()V");
            int idx = this.getLocalVariableIndex(symbol);
            this.emit(Instruction.ASTORE, ""+idx);
    
            //这条语句的作用是,把接下来生成的指令先缓存起来,而不是直接写入到文件里
            this.setClassDefinition(true);
    
            this.emitDirective(Directive.CLASS_PUBLIC, struct.getTag());
            this.emitDirective(Directive.SUPER, "java/lang/Object");
    
            /*
             * 把结构体中的每个成员转换成相应的具有public性质的java类成员
             */
            Symbol fields = struct.getFields();
            do {
                String fieldName = fields.getName() + " ";
                if (fields.getDeclarator(Declarator.ARRAY) != null) {
                    fieldName += "[";
                }
    
                if (fields.hasType(Specifier.INT)) {
                    fieldName += "I";
                } else if (fields.hasType(Specifier.CHAR)) {
                    fieldName += "C";
                } else if (fields.hasType(Specifier.CHAR) && fields.getDeclarator(Declarator.POINTER) != null) {
                    fieldName += "Ljava/lang/String;";
                }
    
                this.emitDirective(Directive.FIELD_PUBLIC, fieldName);
                fields = fields.getNextSymbol();
            }while (fields != null);
    
            /*
             * 实现类的初始构造函数,它调用父类的构造函数后,接下来通过putfield指令,把类的每个成员都初始化为0
             */
            this.emitDirective(Directive.METHOD_PUBLIC, "<init>()V");
    
            this.emit(Instruction.ALOAD, "0");
            String superInit = "java/lang/Object/<init>()V";
            this.emit(Instruction.INVOKESPECIAL, superInit);
    
            fields = struct.getFields();
            do {
                this.emit(Instruction.ALOAD, "0");
                String fieldName = struct.getTag() + "/" + fields.getName();
                String fieldType = "";
                if (fields.hasType(Specifier.INT)) {
                    fieldType = "I";
                    this.emit(Instruction.SIPUSH, "0");
                } else if (fields.hasType(Specifier.CHAR)) {
                    fieldType = "C";
                    this.emit(Instruction.SIPUSH, "0");
                } else if (fields.hasType(Specifier.CHAR) && fields.getDeclarator(Declarator.POINTER) != null) {
                    fieldType = "Ljava/lang/String;";
                    this.emit(Instruction.LDC, " ");
                }
    
                String classField = fieldName + " " + fieldType;
                this.emit(Instruction.PUTFIELD, classField);
    
                fields = fields.getNextSymbol();
            }while (fields != null);
    
            this.emit(Instruction.RETURN);
            this.emitDirective(Directive.END_METHOD);
    
            this.emitDirective(Directive.END_CLASS);
    
            this.setClassDefinition(false);
        }
    ....
    }

    上面代码的作用是把struct定义转换成java的class,并转换成前面讲解过的java类定义的汇编代码,实现的每个步骤都有相应的注释,更详细的讲解和调试请参看视频:用java开发C语言编译器

    我们再看看如何实现对结构体成员变量值的修改:

    public void assignValueToStructMember(Symbol structSym, Symbol field, Object val) {
            //先把类的实例压入堆栈顶部
            int idx = getLocalVariableIndex(structSym);
            this.emit(Instruction.ALOAD, ""+idx);
    
            /*
             * field是要写入的结构体成员对象,假设我们要对myTag.x 赋值,那么下面的代码把myTag.x转换为
             * CTag/x  I
             */
            String value = "";
            String fieldType = "";
            if (field.hasType(Specifier.INT)) {
                fieldType = "I";
                value += (Integer)val;
                this.emit(Instruction.SIPUSH, value);
            } else if (field.hasType(Specifier.CHAR)) {
                fieldType = "C";
                value += (Integer)val;
                this.emit(Instruction.SIPUSH, value);
            } else if (field.hasType(Specifier.CHAR) && field.getDeclarator(Declarator.POINTER) != null) {
                fieldType = "Ljava/lang/String;";
                value += (String)val;
                this.emit(Instruction.LDC, value);
            }
    
            //执行putfield指令,把要修改的值写入结构体成员变量
            Specifier sp = structSym.getSpecifierByType(Specifier.STRUCTURE);
            StructDefine struct = sp.getStructObj();
            String fieldContent = struct.getTag() + "/" + field.getName() + " " + fieldType;
            this.emit(Instruction.PUTFIELD, fieldContent);
    
        }

    实现读取结构体成员变量的代码如下:

    public void readValueFromStructMember(Symbol structSym, Symbol field) {
            /*
             * 先把类的实例加载到堆栈顶部
             */
            int idx = getLocalVariableIndex(structSym);
            this.emit(Instruction.ALOAD, ""+idx);
    
            /*
             * 如果我们要读取myTag.x 下面的语句会构造出
             * CTag/x  I
             */
            String fieldType = "";
            if (field.hasType(Specifier.INT)) {
                fieldType = "I";
            } else if (field.hasType(Specifier.CHAR)) {
                fieldType = "C";
            } else if (field.hasType(Specifier.CHAR) && field.getDeclarator(Declarator.POINTER) != null) {
                fieldType = "Ljava/lang/String;";
            }
    
            //通过getfield指令把结构体的成员变量读出来后压入堆栈顶部
            Specifier sp = structSym.getSpecifierByType(Specifier.STRUCTURE);
            StructDefine struct = sp.getStructObj();
            String fieldContent = struct.getTag() + "/" + field.getName() + " " + fieldType;
            this.emit(Instruction.GETFIELD, fieldContent);
        }

    有了实现结构体定义,结构体成员变量的修改和读取等功能的实现后,我们只要在编译器解析到相应的地方,要执行对应操作时,调用上面代码就可以了。当编译器读取到语句 myTag.x 时,它知道此时程序的目的是想读取结构体成员变量的值,负责解析这条语句的代码是在UnaryNodeExecutor.java中:

    public class UnaryNodeExecutor extends BaseExecutor implements IExecutorReceiver{
    ....
        public Object Execute(ICodeNode root) {
        ....
        case CGrammarInitializer.Unary_StructOP_Name_TO_Unary:
                /*
                 * 当编译器读取到myTag.x 这种类型的语句时,会走入到这里
                 */
                child = root.getChildren().get(0);
                String fieldName = (String)root.getAttribute(ICodeKey.TEXT);
                symbol = (Symbol)child.getAttribute(ICodeKey.SYMBOL);
    
                //先把结构体变量的作用范围设置为定义它的函数名
                symbol.addScope(ProgramGenerator.getInstance().getCurrentFuncName());
                //如果是第一次访问结构体成员变量,那么将结构体声明成一个类
                ProgramGenerator.getInstance().putStructToClassDeclaration(symbol);
    
                if (isSymbolStructPointer(symbol)) {
                    copyBetweenStructAndMem(symbol, false);
                }
    
                /*
                 * 假设当前解析的语句是myTag.x, 那么args对应的就是变量x
                 * 通过调用setStructParent 把args对应的变量x 跟包含它的结构体变量myTag
                 * 关联起来
                 */
                Symbol args = symbol.getArgList();
                while (args != null) {
                    if (args.getName().equals(fieldName)) {
                        args.setStructParent(symbol);
                        break;
                    }
    
                    args = args.getNextSymbol();
                }
    
                if (args == null) {
                    System.err.println("access a filed not in struct object!");
                    System.exit(1);
                }
                /*
                 * 把读取结构体成员变量转换成对应的java汇编代码,也就是使用getfield指令把对应的成员变量的值读取出来,然后压入堆栈顶部
                 */
                if (args.getValue() != null) {
                    ProgramGenerator.getInstance().readValueFromStructMember(symbol, args);
                }
            ....
        ....
        }
    ....
    }

    当代码要对结构体的成员变量赋值时,也就是要执行语句myTag.x = 1;时,编译器的代码会进入Symbol.setValue中,所以在该函数里,我们需要做相应修改如下:

    public class Symbol implements IValueSetter{
    ....
        public void setValue(Object obj) {
            if (obj != null) {
                System.out.println("Assign Value of " + obj.toString() + " to Variable " + name);   
            }
    
            this.value = obj;
    
            if (this.value != null) {
                /*
                 * 先判断该变量是否是一个结构体的成员变量,如果是,那么需要通过assignValueToStructMember来实现成员变量
                 * 的赋值,如果不是,那么就直接通过IStore语句直接赋值
                 */
                ProgramGenerator generator = ProgramGenerator.getInstance();
                if (this.isStructMember() == false) {
                    int idx = generator.getLocalVariableIndex(this);
                    if (generator.isPassingArguments() == false) {
                        generator.emit(Instruction.ISTORE, "" + idx);   
                    }   
                } else {
                    generator.assignValueToStructMember(this.getStructSymbol(), this, this.value);
                }
    
            }
    
        }
    ....
    }

    上面代码完成后,将程序运行起来,前面给定的C语言代码会被编译成如下java汇编代码:

    .class public CSourceToJava
    .super java/lang/Object
    
    .method public static main([Ljava/lang/String;)V
        new CTag
        dup
        invokespecial   CTag/<init>()V
        astore  0
        sipush  1
        aload   0
        sipush  1
        putfield    CTag/x I
        aload   0
        getfield    CTag/x I
        istore  1
        getstatic   java/lang/System/out Ljava/io/PrintStream;
        ldc "value of x in myTag is "
        invokevirtual   java/io/PrintStream/print(Ljava/lang/String;)V
        getstatic   java/lang/System/out Ljava/io/PrintStream;
        iload   1
        invokevirtual   java/io/PrintStream/print(I)V
        getstatic   java/lang/System/out Ljava/io/PrintStream;
        ldc "
    "
        invokevirtual   java/io/PrintStream/print(Ljava/lang/String;)V
        return
    .end method
    .end class
    .class public CTag
    .super java/lang/Object
    .field public c C
    .field public x I
    .method public <init>()V
        aload   0
        invokespecial   java/lang/Object/<init>()V
        aload   0
        sipush  0
        putfield    CTag/c C
        aload   0
        sipush  0
        putfield    CTag/x I
        return
    .end method
    .end class

    把上面的java汇编代码编译成字节码之后运行,结果如下:
    这里写图片描述

    运行结果跟C语言代码的目标是一致的,也就是说,我们把带有struct结构体的C语言代码编译成java字节码是成功的。

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

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

    展开全文
  • 本节,我们研究如何把函数声明和函数调用转换成可执行的java 字节码,在完成本节代码后,我们的编译器能把下面代码编译成可被java 虚拟机执行的字节码,示例代码如下: void f() { printf("execute function f()")...

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

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

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

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

    本节,我们研究如何把函数声明和函数调用转换成可执行的java 字节码,在完成本节代码后,我们的编译器能把下面代码编译成可被java 虚拟机执行的字节码,示例代码如下:

    void f() {
       printf("execute function f()");
    }
    
    void main() {
       f();
    }

    假设java 一个类含有如下方法:

    public float computeSum(float a, float b) {
        float c = a + b;
        return c;
    }

    那么上面的 java代码被编译成汇编语言时,可能会转换成下面这个样子:

    .method public computeSum(FF)F
    .limit locals 3
    .limit stack 2
    float_1    ;把第一个参数从变量数组压入堆栈
    float_2    ;把第二个参数从变量数组压入堆栈
    fadd       ;把堆栈上头两个数值相加,把结果压入堆栈
    freturn    ;把堆栈顶部元素的数值返回
    
    .end method

    上一节,我们讲过,java函数在执行时,java虚拟机会专门分配一个调用场景,在这个场景中,含有一个局部变量数组,用来存储输入参数和局部变量,另个一是操作堆栈,所以指令在执行时,都需要把指令要操作的对象或数值压入到操作堆栈上。

    指令.limit locals 3 告诉虚拟机,在分配局部变量数组时,分配三个单位大小就可以了,一般来说,一个32位机器,一个单位大小就是4字节,因此.limit locals 3 就是要求虚拟机分配12字节大小的局部变量数组。

    同理.limit stack 2 要求虚拟机分配两个单位大小,也就是8字节大小的操作堆栈即可。这两条指令,后面我们在分析虚拟机的代码合法性检测算法时,再详细研究,本节我们知道大概意思即可。

    这节,我们需要重点研究的是,函数的声明。 .method 指令的作用是,告诉虚拟机,接下来的代码是函数的声明和实现。在该指令后面跟着的是函数名和参数列表,形式如下:
    name(type1 type2 type 3….) type_return

    type1 type2 … 描述的是函数的参数类型,type_return 描述的是函数返回值的类型。一个函数,它的调用范围可以是public , protected 以及private,如果他是public 的,那么转换成java汇编时,形式为.method public, 如果是protected,那么转换成汇编时就是.method protected,以此类推。

    函数名,结合函数的输入参数类型,以及函数的返回值,这些信息结合在一起,就形成了函数的签名,例如:
    public void main(String argv[]) 转换成汇编后,它对应的函数签名为:
    .method public main([Ljava/lang/String;)V
    其中,左括号[表示数组,Ljava/lang/String对应的是类对象String, 其中开头的L表示接下来的字符串对应的是一个类变量的声明,如果是基础类型,那么开头就不会出现L,例如int a[] 那么转换为汇编为: [I 其中左括号[表示数组,I表示数据类型为整形。

    如果返回值数据类型为void,那么就在函数输入参数列表后面跟着void对应的类型,也就是大写的字母V.
    下面是一些函数声明转换成java汇编后的样子:
    java: public void static main(String argv[]) ->
    汇编: .method public static main([Ljava/lang/String;)V

    java: public int read(byte[] data, int offset, int len) ->
    汇编: .method public read([BII)I

    java: public void println() ->
    汇编: .method public println()V

    通过上面的分析,我们可以得知,我们要编译的函数void f() 应该转换成:
    .method public static f()V

    由于C语言中,没有类的概念,因此所有除了函数,我们都加上修饰符static.函数中使用的各种操作指令,例如float_1等,我们在后面的章节中在详细研究,这一节,我们知道函数如何声明,以及如何被调用就可以了。

    前一节我们看到,调用一个类的成员函数时,需要的指令是invokevirtual,如果要调用的是类的静态函数,那么需要的指令就是invokestatic,具体使用格式如下:

    invokestatic 类名/方法签名

    有了上面的理论知识后,我们看看如何将C语言的函数调用转换成对应的java虚拟机代码。

    我们解释器的一个特点是,读取到一条语句,我们就执行该语句,所以在把C编译成java字节码时,也是读取一条语句才顺便编译成一条语句。于是在本节开头的代码中,函数f 比mai声明的早,但我们的解释器遇到它时并没有立马就将它转换成java字节码,而是等到main函数中,对函数f进行调用时,解释器才会在执行f的过程中,把f编译成java字节码。

    我们看看实现代码,生成java汇编语言的类分别是CodeGenerator,以及ProgramGenerator,他们的代码如下:

    package backend.Compiler;
    import java.io.File;
    import java.io.FileNotFoundException;
    import java.io.PrintStream;
    import java.io.PrintWriter;
    import java.util.ArrayList;
    import java.util.HashMap;
    
    public class CodeGenerator {
        private  PrintWriter assemblyFile;
        private  int instructionCount = 0;
        private  boolean buffered = false;
        private  String bufferedContent = "";
        protected static String programName = "CSourceToJava";
        private HashMap<String, String>nameToDeclaration = new HashMap<String, String>();
    
        public void setNameAndDeclaration(String name, String declaration) {
            nameToDeclaration.put(name, declaration);
        }
    
        public String getDeclarationByName(String name) {
            return nameToDeclaration.get(name);
        }
    
    
        public void setInstructionBuffered(boolean isBuffer) {
           this.buffered = isBuffer;
        }
    
        public CodeGenerator() {
    
            String assemblyFileName = programName + ".j";
    
            try {
                assemblyFile = new PrintWriter(new PrintStream(new 
                        File(assemblyFileName)));
            } catch (FileNotFoundException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    
        public void emitString(String s) {
            if (buffered) {
                bufferedContent += s + "\n";
                return;
            } 
    
            assemblyFile.print(s);
            assemblyFile.flush();
    
        }
    
        protected void emitBufferedContent() {
            if (bufferedContent.isEmpty() == false) {
                assemblyFile.println(bufferedContent);
                assemblyFile.flush();
            }
        }
    
        public void emitDirective(Directive directive) {
            if (buffered) {
                bufferedContent += directive.toString() + "\n";
                return;
            } 
    
            assemblyFile.println(directive.toString());
            assemblyFile.flush();
            ++instructionCount;
        }
    
        public void emitDirective(Directive directive, String operand) {
            if (buffered) {
                bufferedContent += directive.toString() + " " + operand + "\n";
                return;
            } 
            assemblyFile.println(directive.toString() + " " + operand);
            assemblyFile.flush();
            ++instructionCount;
        }
    
        public void emitDirective(Directive directive, int operand) {
            if (buffered) {
                bufferedContent += directive.toString() + " " + operand + "\n";
                return;
            }
    
            assemblyFile.println(directive.toString() + " " + operand);
            ++instructionCount;
        }
    
        public void emitDirective(Directive directive, String operand1, String operand2) {
            if (buffered) {
                bufferedContent += directive.toString() + " " + operand1 + " " + operand2 + "\n";
                return;
            }
    
            assemblyFile.println(directive.toString() + " " + operand1 + " " + operand2);
            ++instructionCount;
        }
    
        public void emitDirective(Directive directive, String operand1, String operand2, String operand3) {
            if (buffered) {
                bufferedContent += directive.toString() + " " + operand1 + " " + operand2 + " " + operand3 + "\n";
                return;
            }
    
            assemblyFile.println(directive.toString() + " " + operand1 + " " + operand2 + " " + operand3);
            ++instructionCount;
        }
    
        public void emit(Instruction opcode) {
            if (buffered) {
                bufferedContent += "\t" + opcode.toString() + "\n";
                return;
            }
    
            assemblyFile.println("\t" + opcode.toString());
            assemblyFile.flush();
            ++instructionCount;
        }
    
        public void emit(Instruction opcode, String operand) {
            if (buffered) {
                bufferedContent += "\t" + opcode.toString() + "\t" + operand + "\n";
                return;
            }
    
            assemblyFile.println("\t" + opcode.toString() + "\t" + operand);
            assemblyFile.flush();
            ++instructionCount;
        }
    
        public void emitBlankLine() {
            if (buffered) {
                bufferedContent += "\n";
                return;
            }
            assemblyFile.println();
            assemblyFile.flush();
        }
    
        public void finish() {
            assemblyFile.close();
        }
    
    
    }
    

    相比于以前的变化是,这里添加了一个变量buffered,如果它是true,那么输出指令时,先把要输出的代码字符串缓存到变量bufferedContent中。在解释器的实现中,处理函数声明的类为FunctDeclExecutor, 在这个类中,我们根据函数名和参数类型,构造函数的签名,代码如下:

    package backend;
    
    import java.util.ArrayList;
    import java.util.Collections;
    
    import backend.Compiler.Directive;
    import backend.Compiler.ProgramGenerator;
    import frontend.CGrammarInitializer;
    import frontend.Declarator;
    import frontend.Specifier;
    import frontend.Symbol;
    import frontend.TypeSystem;
    
    public class FunctDeclExecutor extends BaseExecutor {
        private ArrayList<Object> argsList = null;
        private ICodeNode currentNode;
        ProgramGenerator generator = ProgramGenerator.getInstance();
    
        @Override
        public Object Execute(ICodeNode root) {
            int production = (Integer)root.getAttribute(ICodeKey.PRODUCTION);
            Symbol symbol = null;
            currentNode = root;
    
            switch (production) {
            case CGrammarInitializer.NewName_LP_RP_TO_FunctDecl:
                root.reverseChildren();
                ICodeNode n = root.getChildren().get(0);
                String name = (String)n.getAttribute(ICodeKey.TEXT);
                if (name != null && name.equals("main") != true) {
                    String declaration = name+"()V";
                    generator.emitDirective(Directive.METHOD_PUBBLIC_STATIC, declaration);
                    generator.setNameAndDeclaration(name, declaration);
                }
                copyChild(root, root.getChildren().get(0));
                break;
    
            case  CGrammarInitializer.NewName_LP_VarList_RP_TO_FunctDecl:
                n = root.getChildren().get(0);
                name = (String)n.getAttribute(ICodeKey.TEXT);
                if (name != null && name.equals("main") != true) {
                    String declaration = name + emitArgs();
                    generator.emitDirective(Directive.METHOD_PUBBLIC_STATIC, declaration);
                    generator.setNameAndDeclaration(name, declaration);
                }
    
                symbol = (Symbol)root.getAttribute(ICodeKey.SYMBOL);
                //获得参数列表
                Symbol args = symbol.getArgList();
                initArgumentList(args);
    
    
                if (args == null || argsList == null || argsList.isEmpty()) {
                    //如果参数为空,那就是解析错误
                    System.err.println("Execute function with arg list but arg list is null");
                    System.exit(1);
                }
    
                break;
            }
            return root;
        }
    
        private void initArgumentList(Symbol args) {
            if (args == null) {
                return;
            }
    
    
            argsList = FunctionArgumentList.getFunctionArgumentList().getFuncArgList(true);
            Symbol eachSym = args;
            int count = 0;
            while (eachSym != null) {
                IValueSetter setter = (IValueSetter)eachSym;
                try {
                    /*
                     * 将每个输入参数设置为对应值并加入符号表
                     */
                    setter.setValue(argsList.get(count));
                    count++;
                } catch (Exception e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
    
                eachSym = eachSym.getNextSymbol();
            }
        }
    
    
        private String emitArgs() {
            argsList = FunctionArgumentList.getFunctionArgumentList().getFuncArgList(true);
            String args = "(";
            for (int i = 0; i < argsList.size(); i++) {
                Symbol symbol = (Symbol)argsList.get(i);
                String arg = "";
                if (symbol.getDeclarator(Declarator.ARRAY) != null) {
                    arg += "[";
                }
    
                if (symbol.hasType(Specifier.INT)) {
                    arg += "I";
                }
    
                args += arg;
            }
    
            args += ")V";
    
            generator.emitString(args);
    
            return args;
        }
    
    
    }
    

    其中,如果函数是没有输入参数的,那么下面的代码会被执行:

    case CGrammarInitializer.NewName_LP_RP_TO_FunctDecl:
    ....
    if (name != null && name.equals("main") != true) {
                    String declaration = name+"()V";
                    generator.emitDirective(Directive.METHOD_PUBBLIC_STATIC, declaration);
                    generator.setNameAndDeclaration(name, declaration);
                }
    ....

    在if判断里面,我们根据被调用的函数名,后加一对括号,然后接着一个大写V,B表示返回值类型为void, 这里,我们暂时假设函数的返回值类型都是void。

    如果调用函数是有参数的,则进入第二个case,在initArgument调用中,调用函数emitArgs,根据参数的类型输出参数列表,这里,我们暂时只处理数组和整形类型。

    当函数f被调用时,解释器才开始对f进行编译,执行函数调用时,解释器才会开始把函数f编译成java汇编代码,执行函数调用的代码在UnaryNodeExecutor中,代码如下:

    case CGrammarInitializer.Unary_LP_RP_TO_Unary:
    case CGrammarInitializer.Unary_LP_ARGS_RP_TO_Unary:
    ....
    ICodeNode func = CodeTreeBuilder.getCodeTreeBuilder().getFunctionNodeByName(funcName);
                if (func != null) {
                    Executor executor = ExecutorFactory.getExecutorFactory().getExecutor(func);
                    ProgramGenerator.getInstance().setInstructionBuffered(true);
                    executor.Execute(func);
                    ProgramGenerator.getInstance().emitDirective(Directive.END_METHOD);
                    ProgramGenerator.getInstance().setInstructionBuffered(false);
                    compileFunctionCall(funcName);
    
                    Object returnVal = func.getAttribute(ICodeKey.VALUE);
                    if (returnVal != null) {
                        System.out.println("function call with name " + funcName + " has return value that is " + returnVal.toString());
                        root.setAttribute(ICodeKey.VALUE, returnVal);
                    }
                }
    ....  

    在执行被调函数,也就是语句executor.Execute(func)之前,我们先通过setInstructionBuffered 将CodeGenerator的 buffered变量设置为true,这样,当生成任何代码时,都将会被缓存起来,而不是直接写入到.j文件。

    当函数f执行时,解释器开始把f编译成java汇编,在函数f内部调用了printf函数,该函数如何转换成对应的java汇编指令,我们前一节已经解释过了,当函数f解释执行完毕后,程序回到上面代码executor.Execute(func)语句的下一句去执行,在程序中,由于函数内部的代码已经全部编译完毕,因此我们输出指令Directive.END_METHOD 用来表示当前是函数的结尾。接着通过通过调用setInstructionBuffered(false)告诉代码转换器CodeGenerator,接下来直接输出编译代码而不需要缓存。函数compileFunctionCall(funcName)的作用是把函数调用转换为java虚拟机中,对函数进行调用的指令,例如:
    f()
    会被转换成:
    invokestatic CSourceToJava/f()V

    接下来,ProgramGenerator也做一些微调,情况如下:

    package backend.Compiler;
    
    public class ProgramGenerator extends CodeGenerator {
        private static ProgramGenerator instance = null;
    
        public static ProgramGenerator getInstance() {
            if (instance == null) {
                instance = new ProgramGenerator();
            }
    
            return instance;
        }
    
        private ProgramGenerator()  {
    
        }
    
        public String getProgramName() {
            return programName;
        }
    
        public void generateHeader() {
            emitDirective(Directive.CLASS_PUBLIC, programName);
            emitDirective(Directive.SUPER, "java/lang/Object");
            emitBlankLine();
            emitDirective(Directive.METHOD_PUBBLIC_STATIC, "main([Ljava/lang/String;)V");
        }
    
    
    
        public void finish() {
            emit(Instruction.RETURN);
            emitDirective(Directive.END_METHOD);
            emitBufferedContent();
            emitDirective(Directive.END_CLASS);
    
            super.finish();
        }
    }
    

    最后在解释器的入口函数处,也做一些改变:

    package frontend;
    
    import backend.CodeTreeBuilder;
    import backend.Intepretor;
    import backend.Compiler.ProgramGenerator;
    
    public class BottomUpParser {
        public static void main(String[] args) {
            /*
             * 把ProductionManager , FirstSetBuilder 两个类的初始化移到CGrammarInitializer
             * 将SymbolDefine 修改成CTokenType, 确定表达式的first set集合运算正确
             */
            ProductionManager productionManager = ProductionManager.getProductionManager();
            productionManager.initProductions();
            productionManager.printAllProductions();
            productionManager.runFirstSetAlgorithm();
    
    
    
            GrammarStateManager stateManager = GrammarStateManager.getGrammarManager();
            stateManager.buildTransitionStateMachine();
    
            System.out.println("Input string for parsing:");
            LRStateTableParser parser = new LRStateTableParser(new Lexer());
            parser.parse();
    
            ProgramGenerator generator = ProgramGenerator.getInstance();
            generator.generateHeader();
    
            //java assembly code will created when intepretor iterate every code node
            CodeTreeBuilder treeBuilder = CodeTreeBuilder.getCodeTreeBuilder();
            Intepretor intepretor = Intepretor.getIntepretor();
            if (intepretor != null) {
                intepretor.Execute(treeBuilder.getCodeTreeRoot());  
            }
    
            generator.finish();
        }
    }
    

    当上面代码完成后,我们给定的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
        getstatic   java/lang/System/out Ljava/io/PrintStream;
        ldc "execute function f()"
        invokevirtual   java/io/PrintStream/println(Ljava/lang/String;)V
        return
    .end method
    
    .end class
    

    有了上面代码后,通过Java汇编编译器,把上面代码转换成java字节码,命令如下:
    java -cp oolong.jar COM.sootNsmoke.oolong.Oolong CSourceToJava.j

    运行上面命令后,在本地目录下会产生一股CSourceToJava.class文件,接着在控制台上,通过java命令运行该字节码文件后,得到结果如下:

    这里写图片描述

    根据运行结果,可以检测,我们编译器编出的java汇编语言是正确的。更详实的代码解读和代码调试说明,请参看视频。

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

    展开全文
  • 在上一节我们把C代码转换成java字节码的例子中,C语言函数含有两个局部变量,这两个变量会对应于队列上的某两个元素,他们到底对应队列上哪个元素是可以由代码指定的,只要访问变量时,从队列的相应位置读取就可以,...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,512
精华内容 604
关键字:

java开发c编译器

java 订阅