java开发C编译器:把struct编译成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语言编译器

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值