java开发C编译器:把struct编译成class

来源:互联网 发布:mac mpv可以加字幕吗 编辑:程序博客网 时间:2024/05/21 13:18

更详细的讲解和代码调试演示过程,请参看视频
用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 ClassNamedupinvokespecial 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   0invokespecial   java/lang/Object/<init>()V

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

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

aload 0sipush 0putfield CTag/c  C

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

aload 0sipush 0putfield 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语言编译器

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

阅读全文
0 0
原创粉丝点击