編程語言
在介紹編譯和反編譯之前,我們先來簡單介紹下編程語言(Programming Language)。編程語言(Programming Language)分為低級語言(Low-level Language)和高級語言(High-level Language)。
機器語言(Machine Language)和匯編語言(Assembly Language)屬於低級語言,直接用計算機指令編寫程序。
而C、C++、Java、Python等屬於高級語言,用語句(Statement)編寫程序,語句是計算機指令的抽象表示。
舉個例子,同樣一個語句用C語言、匯編語言和機器語言分別表示如下:
計算機只能對數字做運算,符號、聲音、圖像在計算機內部都要用數字表示,指令也不例外,上表中的機器語言完全由十六進制數字組成。最早的程序員都是直接用機器語言編程,但是很麻煩,需要查大量的表格來確定每個數字表示什么意思,編寫出來的程序很不直觀,而且容易出錯,於是有了匯編語言,把機器語言中一組一組的數字用助記符(Mnemonic)表示,直接用這些助記符寫出匯編程序,然后讓匯編器(Assembler)去查表把助記符替換成數字,也就把匯編語言翻譯成了機器語言。
但是,匯編語言用起來同樣比較復雜,后面,就衍生出了Java、C、C++等高級語言。
什么是編譯
上面提到語言有兩種,一種低級語言,一種高級語言。可以這樣簡單的理解:低級語言是計算機認識的語言、高級語言是程序員認識的語言。
那么如何從高級語言轉換成低級語言呢?這個過程其實就是編譯。
從上面的例子還可以看出,C語言的語句和低級語言的指令之間不是簡單的一一對應關系,一條a=b+1
;語句要翻譯成三條匯編或機器指令,這個過程稱為編譯(Compile),由編譯器(Compiler)來完成,顯然編譯器的功能比匯編器要復雜得多。用C語言編寫的程序必須經過編譯轉成機器指令才能被計算機執行,編譯需要花一些時間,這是用高級語言編程的一個缺點,然而更多的是優點。首先,用C語言編程更容易,寫出來的代碼更緊湊,可讀性更強,出了錯也更容易改正。
將便於人編寫、閱讀、維護的高級計算機語言所寫作的源代碼程序,翻譯為計算機能解讀、運行的低階機器語言的程序的過程就是編譯。負責這一過程的處理的工具叫做編譯器
現在我們知道了什么是編譯,也知道了什么是編譯器。不同的語言都有自己的編譯器,Java語言中負責編譯的編譯器是一個命令:javac
javac是收錄於JDK中的Java語言編譯器。該工具可以將后綴名為.java的源文件編譯為后綴名為.class的可以運行於Java虛擬機的字節碼。
當我們寫完一個HelloWorld.java
文件后,我們可以使用javac HelloWorld.java
命令來生成HelloWorld.class
文件,這個class
類型的文件是JVM可以識別的文件。通常我們認為這個過程叫做Java語言的編譯。其實,class
文件仍然不是機器能夠識別的語言,因為機器只能識別機器語言,還需要JVM再將這種class
文件類型字節碼轉換成機器可以識別的機器語言。
什么是反編譯
反編譯的過程與編譯剛好相反,就是將已編譯好的編程語言還原到未編譯的狀態,也就是找出程序語言的源代碼。就是將機器看得懂的語言轉換成程序員可以看得懂的語言。Java語言中的反編譯一般指將class
文件轉換成java
文件。
有了反編譯工具,我們可以做很多事情,最主要的功能就是有了反編譯工具,我們就能讀得懂Java編譯器生成的字節碼。如果你想問讀懂字節碼有啥用,那么我可以很負責任的告訴你,好處大大的。比如我的博文幾篇典型的原理性文章,都是通過反編譯工具得到反編譯后的代碼分析得到的。如深入理解多線程(一)——Synchronized的實現原理、深度分析Java的枚舉類型—-枚舉的線程安全性及序列化問題、Java中的Switch對整型、字符型、字符串型的具體實現細節、Java的類型擦除等。我最近在GitChat寫了一篇關於Java語法糖的文章,其中大部分內容都用到反編譯工具來洞悉語法糖背后的原理。
Java反編譯工具
本文主要介紹3個Java的反編譯工具:javap、jad和cfr
javap
javap
是jdk自帶的一個工具,可以對代碼反編譯,也可以查看java編譯器生成的字節碼。javap
和其他兩個反編譯工具最大的區別是他生成的文件並不是java
文件,也不像其他兩個工具生成代碼那樣更容易理解。拿一段簡單的代碼舉例,如我們想分析Java 7中的switch
是如何支持String
的,我們先有以下可以編譯通過的源代碼:
public class switchDemoString { public static void main(String[] args) { String str = "world"; switch (str) { case "hello": System.out.println("hello"); break; case "world": System.out.println("world"); break; default: break; } } }
執行以下兩個命令:
javac switchDemoString.java javap -c switchDemoString.class
生成代碼如下:
public class com.hollis.suguar.switchDemoString { public com.hollis.suguar.switchDemoString(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return public static void main(java.lang.String[]); Code: 0: ldc #2 // String world 2: astore_1 3: aload_1 4: astore_2 5: iconst_m1 6: istore_3 7: aload_2 8: invokevirtual #3 // Method java/lang/String.hashCode:()I 11: lookupswitch { // 2 99162322: 36 113318802: 50 default: 61 } 36: aload_2 37: ldc #4 // String hello 39: invokevirtual #5 // Method java/lang/String.equals:(Ljava/lang/Object;)Z 42: ifeq