由于本節(jié)代碼邏輯有點(diǎn)復(fù)雜醋虏,請參看視頻用java開發(fā)C語言編譯器 以便加深理解和掌握
上一節(jié)寻咒,我們在C程序中引入結(jié)構(gòu)體,在編譯成java字節(jié)碼時颈嚼,我們把結(jié)構(gòu)體轉(zhuǎn)換成一個只包含公有數(shù)據(jù)成員的類毛秘,于是我們把含有結(jié)構(gòu)體的C代碼成功編譯成了java字節(jié)碼,這節(jié)我們要在上一節(jié)的基礎(chǔ)上加大難度阻课,把結(jié)構(gòu)體變成結(jié)構(gòu)體數(shù)組叫挟,看看我們的編譯器是如何把含有結(jié)構(gòu)體數(shù)組的C代碼編譯成java字節(jié)碼的。完成本節(jié)代碼后限煞,我們的編譯器能把下面C代碼編譯成java字節(jié)碼并能在虛擬機(jī)上正確運(yùn)行:
struct CTag {
int x;
char c;
};
void main() {
struct CTag myTag[5];
myTag[2].x = 1;
printf("the x value of second struct object is :%d", myTag[2].x);
}
end
我們把C語言中的結(jié)構(gòu)體等價于java虛擬機(jī)上的一個類抹恳,那么結(jié)構(gòu)體數(shù)組自然就可以對應(yīng)于java上的類數(shù)組,由此我們先看看jvm提供了那些指令讓我們操作類數(shù)組署驻,以及這些指令的用法奋献。
在jvm上健霹,要想生成一個類數(shù)組,需要用到的指令是anewarray,在使用這個指令之前瓶蚂,我們需要在堆棧上壓入一個數(shù)值糖埋,用于表示要生成的數(shù)組長度。假定有一行java代碼如下:
String[] ss = new String[5];
要把上面的代碼轉(zhuǎn)換成虛擬機(jī)字節(jié)碼時扬跋,我們需要這么做阶捆,首先把數(shù)組的元素個數(shù)5壓入到堆棧凌节,然后使用anewarray 指令在堆棧上創(chuàng)建一個String數(shù)組對象钦听,代碼如下:
sipush 5
anewarray java/lang/String
anewarray 指令后面跟著類的類型,上面的指令在堆棧上生成了一個字符串?dāng)?shù)組對象倍奢,字符串含有5個元素朴上,每個元素是一個指向heap上String類型實例的引用,由于我們只是生成了一個含有5個元素的數(shù)組卒煞,jvm會自動把數(shù)組中的五個元素初始化為null,上面字節(jié)碼執(zhí)行后痪宰,虛擬機(jī)的堆棧情況如下:
注意到,anewarray 指令在堆棧頂部生成了一個引用畔裕,這個引用指向了一個存在heap里的一個含有5個字符串類型的數(shù)組實例衣撬,并且實例中的每個元素都指向null.
要想使用字符串?dāng)?shù)組,我們就必須使得數(shù)組中的元素都指向一個字符串實例扮饶,這就需要使用到指令aastore, 和 aaload, 例如我們想讓字符串?dāng)?shù)組的第0和第1個元素分別指向字符串"hello"和"world", 那么我們需要確保anewarray生成的字符串?dāng)?shù)組對象在堆棧頂部具练,接著把數(shù)組元素的下標(biāo)壓入堆棧,最后再把具體字符串壓入堆棧頂部甜无,然后執(zhí)行一次aastore指令扛点,于是要想讓ss[0]指向字符串"hello",相應(yīng)的指令如下(承接上面代碼岂丘,于是字符串?dāng)?shù)組對象已經(jīng)存在堆棧頂部):
astore 0
aload 0
sipush 0
ldc "hello"
aastore
由于ss[0]對象已經(jīng)存在堆棧上陵究,所以指令astore 0先把它存儲到局部變量隊列的第0個位置,然后用aload 0再次把它從局部隊列加載到堆棧頂部奥帘,這么做看似多此一舉铜邮,但這對后面的指令有作用。然后把要賦值的數(shù)組元素下標(biāo)放到堆棧頂部寨蹋,也就對應(yīng)sipush 0松蒜, 最后把數(shù)組元素0要引用的字符串"hello"壓到堆棧頂部,因此在執(zhí)行指令aastore前钥庇,堆棧情況如下:
stack: ss[0] "hello" 0
執(zhí)行指令aastore后牍鞠,元素ss[0]就不再是null了,它會指向字符串"hello"评姨。指令aastore執(zhí)行后难述,堆棧上所有元素會被清空萤晴。為了把字符串?dāng)?shù)組下標(biāo)為1的元素指向字符串"world",我們需要把字符串?dāng)?shù)組對象重新加載到堆棧上,所以需要執(zhí)行指令aload 0, 接著把下標(biāo)1壓入堆棧胁后,最后把字符串"world"壓入堆棧店读,然后再次執(zhí)行aastore指令,相關(guān)代碼如下:
aload 0
sipush 0
ldc "world"
aastore
上面兩段代碼執(zhí)行后攀芯,堆棧情況如下:
既然數(shù)組的元素0和1都已經(jīng)指向了兩個有效的字符串屯断,如果程序想要通過這兩個數(shù)組元素獲取他們指向的字符串,那么就需要使用指令aaload,例如要想訪問ss[0]執(zhí)行的字符串"hello"侣诺,那么我們需要把字符串?dāng)?shù)組對象加載到堆棧頂部殖演,燃爆把元素下標(biāo)壓入堆棧,然后執(zhí)行aaload指令年鸳,代碼如下:
aload 0
sipush 0
aaload
上面代碼執(zhí)行后趴久,堆棧頂部存儲的是一個String類型的引用,這個引用指向存儲在Heap里面的字符串實例"hello"搔确”斯鳎基于這些原理,我們就可以把C語言中含有結(jié)構(gòu)體數(shù)組的代碼編譯成java字節(jié)碼了膳算,我們完全可以照貓畫虎座硕,把上面的String對象換成結(jié)構(gòu)體對象就可以了。
回到前面的C語言代碼涕蜂,對照上面的原理华匾,我們看看如何把含有結(jié)構(gòu)體數(shù)組的C語言代碼編譯成字節(jié)碼。編譯器解讀代碼時宇葱,當(dāng)解讀到這一句:struct CTag myTag[5];瘦真,它會創(chuàng)建一個Symbol對象,該對象對應(yīng)的變量名稱為myTag,并記錄下黍瞧,它是一個含有5個元素的數(shù)組诸尽。
當(dāng)編譯器讀取語句:myTag[2].x = 1;時,它會使用anewarray指令生成一個CTag類的數(shù)組印颤,按照前面講過的指令用法您机,我們的編譯器會產(chǎn)生如下指令:
(代碼片段1)
sipush 5
anewarray CTag
astore 0
同時編譯器此時知道,代碼想對數(shù)組中的第二個對象中的x成員賦值為1年局,前面講過际看,anewarray 指令只是生成了數(shù)組對象,數(shù)組中的每個元素會被初始化為null,此時要對第二個元素指向的對象進(jìn)行賦值矢否,那么就必須為第二個元素生成一個CTag類的實例仲闽,于是編譯器要接著生成如下指令,(代碼片段二):
aload 0
sipush 2
new CTag
dup
invokespecial CTag/<init>()V
aastore
上面指令把CTag類型數(shù)組對象加載到堆棧上后僵朗,把數(shù)組元素的下標(biāo)2壓入堆棧赖欣,然后創(chuàng)建一個CTag實例對象屑彻,初始化后,通過aastore指令把該對象的引用存入到數(shù)組對象的第二個元素顶吮。
接著代碼需要對第二個元素指向的實例所包含的成員x賦值社牲,于是編譯器需要生成指令,把數(shù)組第二個元素引用的實例加載到堆棧上悴了,然后使用上一節(jié)講過的方法對類成員賦值搏恤,于是它會產(chǎn)生如下指令,(代碼片段3):
aload 0
sipush 2
aaload
sipush 1
putfield CTag/x I
aload 0 把數(shù)組對象加載到堆棧后湃交,壓入要訪問的元素下標(biāo)2熟空,通過aaload指令把元素二引用的類實例加載到堆棧上,使用上節(jié)講過的指令修改類實例的成員變量巡揍。
最后編譯器解讀到語句:printf("the x value of second struct object is :%d", myTag[2].x); 痛阻,這條語句中,我們需要讀取數(shù)組第二個元素指向?qū)嵗念惓蓡Tx,那么編譯器需要生成指令腮敌,把它加載到堆棧上,然后使用上一節(jié)的辦法俏扩,讀取類實例的成員變量值糜工,于是會生成如下代碼(指令片段4):
aload 0
sipush 2
aaload
getfield CTag/x I
上面指令先把數(shù)組對象加載到堆棧上,然后把要訪問的數(shù)值元素下標(biāo)2壓到堆棧录淡,接著使用aaload指令把元素2指向的類實例加載到堆棧上捌木,然后使用指令getfield 獲取該實例成員變量x 的值。
按照上面描述的指令生成步驟嫉戚,我們要對編譯器做相應(yīng)代碼修改刨裆。在解析語句myTag[2].x = 1;時,代碼會進(jìn)入到UnaryNodeExecutor.java中彬檀,于是我們做如下代碼修改:
case CGrammarInitializer.Unary_LB_Expr_RB_TO_Unary:
child = root.getChildren().get(0);
symbol = (Symbol)child.getAttribute(ICodeKey.SYMBOL);
child = root.getChildren().get(1);
int index = (Integer)child.getAttribute(ICodeKey.VALUE);
try {
Declarator declarator = symbol.getDeclarator(Declarator.ARRAY);
if (declarator != null) {
Object val = declarator.getElement(index);
root.setAttribute(ICodeKey.VALUE, val);
ArrayValueSetter setter = new ArrayValueSetter(symbol, index);
root.setAttribute(ICodeKey.SYMBOL, setter);
root.setAttribute(ICodeKey.TEXT, symbol.getName());
//create array object on jvm
if (symbol.getSpecifierByType(Specifier.STRUCTURE) == null) {
//如果是結(jié)構(gòu)體數(shù)組帆啃,這里不做處理,留給下一步處理
ProgramGenerator.getInstance().createArray(symbol);
ProgramGenerator.getInstance().readArrayElement(symbol, index);
}
}
....
當(dāng)編譯器解析到數(shù)組讀取時會執(zhí)行上面代碼窍帝,同時生成與數(shù)組讀取相關(guān)的jvm指令努潘,這里我們先判斷讀取的數(shù)組是否是結(jié)構(gòu)體,如果是坤学,那么就不能直接生成數(shù)組讀取指令疯坤。執(zhí)行完上面代碼后,又會進(jìn)入下面的代碼進(jìn)行執(zhí)行:
case CGrammarInitializer.Unary_StructOP_Name_TO_Unary:
/*
* 當(dāng)編譯器讀取到myTag.x 這種類型的語句時深浮,會走入到這里
*/
child = root.getChildren().get(0);
String fieldName = (String)root.getAttribute(ICodeKey.TEXT);
Object object = child.getAttribute(ICodeKey.SYMBOL);
boolean isStructArray = false;
if (object instanceof ArrayValueSetter) {
//這里表示正在讀取結(jié)構(gòu)體數(shù)組,先構(gòu)造結(jié)構(gòu)體數(shù)組
symbol = getStructSymbolFromStructArray(object);
symbol.addValueSetter(object);
isStructArray = true;
} else {
symbol = (Symbol)child.getAttribute(ICodeKey.SYMBOL);
}
....
當(dāng)編譯器讀取到類似myTag.x這樣類型的代碼時压怠,它知道此時程序正在對結(jié)構(gòu)體進(jìn)行處理,于是會進(jìn)入到上面的代碼飞苇,由于當(dāng)代碼需要讀取數(shù)組時菌瘫,編譯器都會為數(shù)組的讀寫構(gòu)造一個ArrayValueSetter對象洋闽,上面代碼處理的是結(jié)構(gòu)體的讀寫,同時編譯器又發(fā)現(xiàn)傳過來的對象是一個ArrayValueSetter實例突梦,于是編譯器就會明白诫舅,當(dāng)前處理的是一個結(jié)構(gòu)體數(shù)組對象,然后代碼會調(diào)用getStructSymbolFromStructArray,這個函數(shù)的作用是為結(jié)構(gòu)體數(shù)組下標(biāo)為2的元素生成一個結(jié)構(gòu)體對象實例宫患,并且生成前面講過的指令片段1刊懈,我們看看該函數(shù)的實現(xiàn):
private Symbol getStructSymbolFromStructArray(Object object) {
ArrayValueSetter vs = (ArrayValueSetter)object;
Symbol symbol = vs.getSymbol();
int idx = vs.getIndex();
Declarator declarator = symbol.getDeclarator(Declarator.ARRAY);
if (declarator == null) {
return null;
}
ProgramGenerator.getInstance().createStructArray(symbol);
Symbol struct = null;
try {
struct = (Symbol)declarator.getElement(idx);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
if (struct == null) {
struct = symbol.copy();
try {
declarator.addElement(idx, (Object)struct);
//通過指令為數(shù)組中的某個下標(biāo)處創(chuàng)建結(jié)構(gòu)體實例
ProgramGenerator.getInstance().createInstanceForStructArray(symbol, idx);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
return struct;
}
該函數(shù)先為myTag生成一個同類型的結(jié)構(gòu)體Symbol對象,其中代碼執(zhí)行了語句
ProgramGenerator.getInstance().createStructArray(symbol);
這條語句的作用是娃闲,生成前面說過的指令片段1虚汛,同時上面代碼還執(zhí)行了語句:
ProgramGenerator.getInstance().createInstanceForStructArray(symbol, idx);
它的作用是生成指令片段2.這兩個函數(shù)的具體實現(xiàn),在后面我們才羅列出來皇帮,代碼執(zhí)行完后卷哩,回到getStructSymbolFromStructArray函數(shù)的調(diào)用處繼續(xù)往下執(zhí)行:
....
//先把結(jié)構(gòu)體變量的作用范圍設(shè)置為定義它的函數(shù)名
if (isStructArray == true) {
//把結(jié)構(gòu)體數(shù)組symbol對象的作用域范圍設(shè)置為包含它的函數(shù)
ArrayValueSetter vs = (ArrayValueSetter)object;
Symbol structArray = vs.getSymbol();
structArray.addScope(ProgramGenerator.getInstance().getCurrentFuncName());
} else {
symbol.addScope(ProgramGenerator.getInstance().getCurrentFuncName());
}
//如果是第一次訪問結(jié)構(gòu)體成員變量,那么將結(jié)構(gòu)體聲明成一個類
ProgramGenerator.getInstance().putStructToClassDeclaration(symbol);
....
/*
* 假設(shè)當(dāng)前解析的語句是myTag.x, 那么args對應(yīng)的就是變量x
* 通過調(diào)用setStructParent 把a(bǔ)rgs對應(yīng)的變量x 跟包含它的結(jié)構(gòu)體變量myTag
* 關(guān)聯(lián)起來
*/
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);
}
/*
* 把讀取結(jié)構(gòu)體成員變量轉(zhuǎn)換成對應(yīng)的java匯編代碼,也就是使用getfield指令把對應(yīng)的成員變量的值讀取出來属拾,然后壓入堆棧頂部
*/
//TODO 需要區(qū)分結(jié)構(gòu)體是否來自于結(jié)構(gòu)體數(shù)組
if (args.getValue() != null) {
ProgramGenerator.getInstance().readValueFromStructMember(symbol, args);
}
....
代碼先把變量myTag的作用域設(shè)置成包含它的函數(shù)将谊,也就是main函數(shù),然后調(diào)用ProgramGenerator.getInstance().putStructToClassDeclaration(symbol);
將結(jié)構(gòu)體聲明成一個java的類聲明渐白。當(dāng)要讀取結(jié)構(gòu)體數(shù)組第二個元素的成員變量x的時候尊浓,ProgramGenerator.getInstance().readValueFromStructMember(symbol, args);
會被執(zhí)行,它的作用是生成前面講過的指令片段4.
C語言代碼中myTag[2].x = 1; 這條語句的目標(biāo)是對結(jié)構(gòu)體數(shù)組第二個元素的成員變量x賦值1纯衍,編譯器在解釋執(zhí)行這個賦值操作時栋齿,會進(jìn)入到Symbol.java,執(zhí)行如下代碼:
@Override
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) {
/*
* 先判斷該變量是否是一個結(jié)構(gòu)體的成員變量襟诸,如果是瓦堵,那么需要通過assignValueToStructMember來實現(xiàn)成員變量
* 的賦值,如果不是歌亲,那么就直接通過IStore語句直接賦值
*/
ProgramGenerator generator = ProgramGenerator.getInstance();
if (this.isStructMember() == false) {
int idx = generator.getLocalVariableIndex(this);
if (generator.isPassingArguments() == false) {
generator.emit(Instruction.ISTORE, "" + idx);
}
} else {
if (this.getStructSymbol().getValueSetter() != null) {
generator.assignValueToStructMemberFromArray(this.getStructSymbol().getValueSetter(), this, this.value);
} else {
generator.assignValueToStructMember(this.getStructSymbol(), this, this.value);
}
}
}
}
代碼先判斷菇用,當(dāng)前的賦值操作是否是針對結(jié)構(gòu)體數(shù)組中的某個元素進(jìn)行的,如果是应结,那么generator.assignValueToStructMemberFromArray(this.getStructSymbol().getValueSetter(), this, this.value);
這條語句就會執(zhí)行刨疼,它的作用是生成代碼片段3.
由于篇幅原因,generator生成java指令的實現(xiàn)將會在視頻中再做解析鹅龄,更詳細(xì)的講解和代碼調(diào)試演示過程揩慕,請參看視頻
用java開發(fā)C語言編譯器
上面代碼完成后,編譯器會把給定的C語言代碼編譯成如下java匯編語言:
.class public CSourceToJava
.super java/lang/Object
.method public static main([Ljava/lang/String;)V
sipush 2
sipush 5
anewarray CTag
astore 0
aload 0
sipush 2
new CTag
dup
invokespecial CTag/<init>()V
aastore
sipush 1
aload 0
sipush 2
aaload
sipush 1
putfield CTag/x I
sipush 2
aload 0
sipush 2
aaload
getfield CTag/x I
istore 1
getstatic java/lang/System/out Ljava/io/PrintStream;
ldc "the x value of second struct object 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
把這些匯編語言編譯成字節(jié)碼扮休,在虛擬機(jī)上運(yùn)行后迎卤,結(jié)果如下:
通過運(yùn)行結(jié)果可以得知,編譯器生成的字節(jié)碼在虛擬機(jī)上運(yùn)行的結(jié)果跟C語言代碼想要的結(jié)果是完全一樣的玷坠。由于本節(jié)代碼邏輯有點(diǎn)復(fù)雜蜗搔,請參看視頻用java開發(fā)C語言編譯器 以便加深理解和掌握劲藐。
更多技術(shù)信息,包括操作系統(tǒng)樟凄,編譯器聘芜,面試算法,機(jī)器學(xué)習(xí)缝龄,人工智能汰现,請關(guān)照我的公眾號: