jvm的return指令以及局部變數的操作
jvm運行位元組碼時,代碼的運行必須圍繞兩種數據結構,一種是堆棧,一種是隊列,如果jvm執行某條指令時,該指令需要對數據進行操作,那麼被操作的數據在指令執行前,必須要壓倒堆棧上。如果堆棧上的數據需要暫時保持起來時,它就會被載入到局部變數隊列上。
java代碼中,每個方法裡面的局部變數包括函數的輸入參數都會存儲在隊列上。我們看下面一個方法:
jvm在執行上面代碼時,首先會分配一個堆棧和一個隊列,一開始堆棧和隊列都為空:
stack: null
list: null
要執行語句 a = 1; 時,首先需要把常量1壓到堆棧上:
stack: 1
局部變數a對應於隊列的第0個元素,把1賦值給變數a,就相當於把堆棧頂部的數值1轉移到隊列的第0個元素,因此語句a =1;執行後隊列和堆棧的情況如下:
stack: null
list: 1,
執行第二條語句b = 2; 時同理,先把常量2壓到堆棧上:
stack: 2
list: 1
由於變數b是函數的第二個局部變數,因此它對應隊列的第1個元素,把常量2賦值給變數b,就需要把堆棧頂部的數值2移到堆棧的第1個元素:
stack:
list: 1, 2
由此,當我們把C語言編譯成java位元組碼時,在解析函數時,函數中的局部變數都需要對應到虛擬機局部變數隊列中的對應元素,在一會給出的例子中,我們會通過代碼看看,在解析C語言函數的局部變數時,程序是如何把變數和jvm的變數隊列對應起來的。
在此,我們還需要介紹的jvm的return指令, 無論是C代碼還是java代碼,一旦函數有返回值時,都需要通過關鍵字return把數據返回給函數的調用者,代碼中的return語句在編譯成java位元組碼後,對應著多條語句。如果return 語句返回的數據類型是整形,那麼該return語句對應的位元組碼指令是ireturn, 前面的i表示整形,同理,如果return 返回的數據類型是浮點數,那麼對應的java位元組碼指令就是freturn, 前面的f表示float,如果return 返回的數據類型是double,對應的位元組碼指令就是dreturn, 前面的d表示double。
需要注意的是return 語句對應的位元組碼指令必須跟函數的返回值聲明相一致,如果函數聲明時返回的數據類型是整形,結果函數編譯後使用的return指令是freturn,也就是要返回一個浮點數,這種不一致性會被jvm檢測到,一旦發現指令的邏輯不一致,虛擬機就會拒絕執行給定的代碼。
介紹完理論後,我們看看如何把理論付諸實踐。下面的代碼將是我們要編譯成java位元組碼的C語言代碼:
函數f的返回值是int,因此在編譯成java位元組碼時,f 裡面的return語句編譯後要對應上jvm的ireturn指令,同時f中含有兩個局部變數a,b,根據前面講述的理論,在解析到這兩個變數時,編譯器需要把他們對應到java虛擬機中局部變數隊列的相應元素。
我們看看,局部變數是如何對應到虛擬機隊列的相應元素的,在ProgramGenerator.java中,添加代碼如下:
每個變數都對應著一個符號對象Symbol, 同時每個變數都有給定的作用範圍,getSymbolsByScope把同一作用範圍內的變數全部取出來,形成一個隊列,例如在f中,有兩個局部變數a,b , 他們的作用範圍都是f, 如果把a對應的Symbol對象傳入上面函數後,list會得到一個隊列,該隊列包含兩個Symbol對象,這兩個對象就是變數a和b所對應的Symbol對象。得到這個隊列後,我們通過變數符號對象在隊列中的位置來對應他們在虛擬機隊列中的位置。
第二處需要改動的代碼在UnaryNodeExecutor.java中:
在解析到某個變數是,我們先看該變數是否已經賦值了,也就是symbol.getValue()返回值不是null, 如果賦值了,那麼通過調用getLocalVariableIndex得到該變數在遍歷隊列里的位置,這個位置將作為它對應在虛擬機變數隊列里的位置。假設該變數對應的位置是x, 那麼上面代碼將輸出指令:
iload x
也就是把變數隊列中第x個元素載入到堆棧頂端。
第三處需要改動的代碼在FunctDeclExecutor.java:
這裡我們要把C語言中的函數聲明編譯成java位元組碼的函數聲明,原來我們一直默認函數返回值都是void型,現在我們函數可以返回整形了,一個函數本質上也是一個變數,因此函數f也對應著一個Symbol對象,我們通過判斷該Symbol對象的類型就可以得知函數的返回值,以例子代碼為例: int f() 由於f前面有關鍵字int來修飾,因此f對應的Symbol對象它包含一個類型為int的specifier,一旦我們判斷到返回值是整形時,在把函數聲明編譯成位元組碼時,需要在函數參數列表後面加上一個I,用於表明返回值是整形,由此C代碼中的函數聲明int f()編譯成java位元組碼後對應的代碼為:
.method public static f()I。
第四處需要修改的代碼還是在UnaryNodeExecutor.java中:
前面我們提到過,函數聲明時,指明了返回值類型的話,那麼return必須根據返回值類型對應到位元組碼相應的xreturn語句,在這裡我們通過函數的符號對象,獲得函數的返回值類型,如果函數的返回值類型是整形,那麼編譯器就要輸出ireturn語句,如果返回值是void類型,那麼輸出return指令就可以了。
第五處需要修改的是Symbol.java:
在變數被賦值時,上面的代碼會被調用,前面我們講過,如果給局部變數賦值,局部變數對應的是虛擬機隊列中的某個元素,對其賦值,相當於把堆棧頂部的數據轉移到隊列的對應位置上,假設例子中變數b對應在隊列中的位置為1,上面代碼執行後,編譯器會輸出指令:
istore 1
也就是把堆棧頂部的整形數值轉移給隊列中的第一個元素。
上面代碼執行後,我們的編譯器會把給定的C語言程序編譯成如下java彙編代碼:
上面彙編代碼編譯成位元組碼後執行,其結果如下:
通過執行結果可見,我們編譯器對代碼的編譯結果應該是正確的。由於本節將是內容有點抽象,請結合視頻演示一起來閱讀本文,以便獲得更清晰的理解。
更多技術信息,包括操作系統,編譯器,面試演算法,機器學習,人工智慧,請關照我的公眾號:
![](https://pic.pimg.tw/zzuyanan/1488615166-1259157397.png)
![](https://pic.pimg.tw/zzuyanan/1482887990-2595557020.jpg)
TAG:Coding迪斯尼 |
※linux-shell編程中awk變數的使用
※python基礎之變數類型number(math模塊)
※Python和Scala的定義變數
※Flipkart-Walmart交易:軟銀最終會有變數嗎
※關於如何使用webpack命令行傳入變數,並通過process.env來調用
※「學習筆記」Python dir()函數和 __doc__ 變數的使用
※微軟摺疊屏專利現身,Surface Phone或有變數
※「Python」Chapter1 變數和簡單數據類型
※TensorFlow中常量與變數的基本操作演示
※非靜態內部類中 static/final 成員變數相關的一道趣題
※static 成員變數、static 成員函數、類/對象的大小
※VBScript 變數
※Perl 變數
※Shell 變數
※全新斯巴魯Levorg問世有變數 EyeSight成拖累主因
※python基礎之變數與運算符
※The Daily Beast:美朝峰會出現變數,特朗普怪罪中國
※Linux 設置環境變數和Mongodb設置賬號密碼
※Lanchester戰爭模型:用可分離變數的微分方程占卜戰事
※是自走棋還是Roguelike?《變數》讓塔防遊戲重獲新生