程序員如何玩轉彙編指令?
作者 | 帥地
責編 | 郭芮
我想大部分都知道 i++ 和 ++i的區別,i++ 就是先拿i來使用,之後再自增加1,而++i則是先自增加1,在拿i來使用。例如對於下面這兩個語句,我敢保證大部分人都會做:
int i = 1;
System.out.println(i++)
int i = 1;
System.out.println(++1)
答案分別為 1,2,對於這個答案我猜大多數人都能答出來。不過 i++ 和 ++i 這兩個操作,在內部是如何實現的呢?
我們先來看另外一個問題:
public static void main(String[] args) {
int i = 1;
System.out.println(i+++i++);
System.out.println(i);
}
這個比剛才那個難了點,答案分別是3,3。假如你對這個答案的由來了如指掌,那麼你大不可必往下看,假如你不大理解或者想從底層的彙編指令的來了解這個操作,那麼你可以看看我的解釋。
首先我們先來看看 i++ 的題,主要是為了後面好解釋點。
int i = 1;
System.out.println(i++);
這兩行代碼的部分彙編指令如下,注意,我只列出了幾個重點的彙編語句:
ICONST_1 //把常量 1 載入到棧頂
ISTORE 1 //把棧頂的元素彈出,並賦值給局部變數表中位置為「1」的變數,此時指變數i。這兩句就相當於 int i = 1;
//接下來執行第二行代碼
ILOAD 1 //把局部變數表中位置為「1」的變數載入到棧頂,即把i的值載入到棧頂
IINC 1 1 //直接把局部變數表中位置為「1」的變數加1,即把 i 加1。注意,這條指令並沒有修改操作數棧就把 i 加1了。
INVOKEVIRTUAL java/io/PrintStream.println (I)V //把棧頂的元素列印出來,此時棧頂的元素是 1。所以列印的是 1
所以,此時列印的是1。
有些人可能沒弄過彙編會有點懵逼,沒事,我花個時間畫個圖來模擬(註:省略很多細節)。
剛開始時的局部變數表和操作數棧如圖所示:
1、執行 ICONST_1,常量 1 進棧:
2、執行 ISTORE 1,棧頂元素出棧存到位置「1」:
3、執行 ILOAD 1,把位置「1」的變數值存到棧頂:
4、執行 IINC 1 1 ,直接把局部變數表中位置為「1」的變數加 1:
5、執行 INVOKEVIRTUAL java/io/PrintStream.println (I)V,把棧頂的元素列印出來,此時棧頂的元素是 1:
所以雖然i已經等於2了,但此時棧頂的元素卻是i之前的值 1 ,所以列印的是1。
這下關於 i ++ 的懂了吧?
那我們來看看 ++ i 與 i ++ 的彙編指令有什麼不同。
int i = 1;
System.out.println(++i);
對應的部分重點彙編指令如下:
//和上面i++差不多,不過IINC 1 1 和ILOAD 1這兩句的順序調換了。
ICONST_1
ISTORE 1
IINC 1 1 //直接把局部變數表中位置為「1」的變數加1
ILOAD 1 //把位置「1」的變數壓到棧頂,此時棧頂的元素是 2
INVOKEVIRTUAL java/io/PrintStream.println (I)V //所以列印的是2
再畫下圖演示一下:
1、執行了ICONST_1 和ISTORE 1這兩句過後的局部變數和棧的情況如下:
2、執行 IINC 1 1,注意,執行這條指令,操作數棧不會發生變化:
3、執行 ILOAD 1,把位置「1」的變數值壓入棧頂:
4、執行 INVOKEVIRTUAL java/io/PrintStream.println (I)V,把棧頂的元素列印出來,此時棧頂的元素是 2:
所以,對於 i++ 和 ++i的區別徹底懂了吧。
接下來我們來分析這個程序:
int i = 1;
System.out.println(i+++i++);
System.out.println(i);
這裡先說一下,按照運算符號的優先順序,i+++i++等價於 (i++) + (i++)。
對應的部分彙編指令如下:
//第一行
ICONST_1
ISTORE 1
//第二行
ILOAD 1
IINC 1 1
ILOAD 1
IINC 1 1
IADD //把棧頂的兩個元素彈出相加之後在把結果放回棧頂
INVOKEVIRTUAL java/io/PrintStream.println (I)V
//第三行
ILOAD 1
INVOKEVIRTUAL java/io/PrintStream.println (I)V
如果上面的那兩個 i++ 和 ++i 你看懂了,那麼上面那個彙編應該也差不多能看懂。我用圖來逐條分析一下吧。
1、執行了 ICONST_1 和ISTORE 1之後的狀態如下:
2、執行 ILOAD 1:
3、執行 IINC 1 1:
4、執行 ILOAD 1:
5、執行 IINC 1 1:
此時實際上 i 的值已經是 3 了,只是棧頂放的都是 i 的舊值。
6、執行 IADD ,把棧頂兩個元素出棧相加後再把結果入棧:
7、執行INVOKEVIRTUAL java/io/PrintStream.println (I)V,此時棧頂元素為3,所以列印的是3:
8、執行 ILOAD 1,把局部變數表載入到棧頂:
9、執行INVOKEVIRTUAL java/io/PrintStream.println (I)V,此時棧頂元素為3,所以列印的是3:
完畢!
現在知道了吧,對於 i+++++i 的題也知道怎麼做以及怎麼回事了吧。
這篇文章重點讓你理解 i++ 與 ++ i的實現機制,對於上面的彙編指令以及進棧入棧的過程為了更好說明要解決的問題,所以隱藏了很多細節,而且也刪除了部分代碼。如有錯誤的地方,還請見諒。
如果你想了解更多的彙編指令,我這裡看到一篇總結的還挺全的:https://blog.csdn.net/hudashi/article/details/7062675。
聲明:本文為作者投稿,版權歸其個人所有。


TAG:CSDN |