您的位置:首页 > 编程语言 > Java开发

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

2017-08-15 17:27 567 查看
更详细的讲解和代码调试演示过程,请参看视频

用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语言编译器

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

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息