私はちょうど私のクラスのこの逆コンパイルされたクラスファイルに遭遇しました:
MyClass
while ((line = reader.readLine()) != null) {
System.out.println("line: " + line);
if (i == 0) {
colArr = line.split(Pattern.quote("|"));
} else {
i++;
}
}
クラスファイルでwhile
ループがfor
ループに変更されました。
デコンパイルされたMyClass
for (String[] colArr = null; (line = reader.readLine()) != null; ++i) {
System.out.println("line: " + line);
if (i == 0) {
colArr = line.split(Pattern.quote("|"));
} else {
}
}
このループがfor
に変更されたのはなぜですか?コンパイラによるコード最適化の別の方法かもしれないと思うのですが、間違っている可能性があります。 for
ループが他のループよりもwhile
ループが提供する利点は何かを知りたいだけです。
このようなコード最適化のカテゴリは何ですか?
この場合、while()
をfor()
に変更することは最適化ではありません。ソースコードでどのコードが使用されたかをバイトコードから知る方法はありません。
次の場合には多くの状況があります。
while(x)
次と同じです:
for(;x;)
同様の3つのJavaアプリケーションがあり、1つはwhile()
ステートメントで、2つは対応するfor()
であるとします。最初のfor()
は、標準のwhile()
のような停止基準のみを持ち、2番目のfor()
もイテレータ宣言とインクリメントを持ちます。
アプリケーション#1-ソース
public class While{
public static void main(String args[]) {
int i = 0;
while(i<5){
System.out.println(i);
i++;
}
}
}
アプリケーション#2-ソース
public class For{
public static void main(String args[]) {
int i = 0;
for(; i<5 ;){
System.out.println(i);
i++;
}
}
}
アプリケーション#3-ソース
public class For2{
public static void main(String args[]) {
for(int i=0;i<5;i++){
System.out.println(i);
}
}
}
それらをすべてコンパイルすると、次のようになります。
アプリケーション#1-バイトコード
public class While {
public While();
Code:
0: aload_0
1: invokespecial #1 // Method Java/lang/Object."<init>":()V
4: return
public static void main(Java.lang.String[]);
Code:
0: iconst_0
1: istore_1
2: iload_1
3: iconst_5
4: if_icmpge 20
7: getstatic #2 // Field Java/lang/System.out:Ljava/io/PrintStream;
10: iload_1
11: invokevirtual #3 // Method Java/io/PrintStream.println:(I)V
14: iinc 1, 1
17: goto 2
20: return
}
アプリケーション#2-バイトコード
public class For {
public For();
Code:
0: aload_0
1: invokespecial #1 // Method Java/lang/Object."<init>":()V
4: return
public static void main(Java.lang.String[]);
Code:
0: iconst_0
1: istore_1
2: iload_1
3: iconst_5
4: if_icmpge 20
7: getstatic #2 // Field Java/lang/System.out:Ljava/io/PrintStream;
10: iload_1
11: invokevirtual #3 // Method Java/io/PrintStream.println:(I)V
14: iinc 1, 1
17: goto 2
20: return
}
アプリケーション#3-バイトコード
public class For2 extends Java.lang.Object{
public For2();
Code:
0: aload_0
1: invokespecial #1; //Method Java/lang/Object."<init>":()V
4: return
public static void main(Java.lang.String[]);
Code:
0: iconst_0
1: istore_1
2: iload_1
3: iconst_5
4: if_icmpge 20
7: getstatic #2; //Field Java/lang/System.out:Ljava/io/PrintStream;
10: iload_1
11: invokevirtual #3; //Method Java/io/PrintStream.println:(I)V
14: iinc 1, 1
17: goto 2
20: return
}
for
とwhile
の使用法に関連する違いはありません。
他の人がすでに指摘しているように:デコンパイラーは(通常)同じバイトコードをもたらす異なるソースコードを区別できません。
残念ながら、メソッドのfullコードを提供しませんでした。したがって、以下には、メソッド内でこのループがどこでどのように表示されるかについての推測が含まれています(これらの推測は、ある程度、結果を歪める可能性があります)。
しかし、ここでいくつかの往復を見てみましょう。投稿したコードの両方のバージョンのメソッドを含む次のクラスを検討してください。
import Java.io.BufferedReader;
import Java.io.IOException;
import Java.util.regex.Pattern;
public class DecompileExample {
public static void methodA(BufferedReader reader) throws IOException {
String line = null;
int i = 0;
while ((line = reader.readLine()) != null) {
System.out.println("line: " + line);
if (i == 0) {
String[] colArr = line.split(Pattern.quote("|"));
} else {
i++;
}
}
}
public static void methodB(BufferedReader reader) throws IOException {
String line = null;
int i = 0;
for (String[] colArr = null; (line = reader.readLine()) != null; ++i) {
System.out.println("line: " + line);
if (i == 0) {
colArr = line.split(Pattern.quote("|"));
} else {
}
}
}
}
でコンパイルする
javac DecompileExample.Java -g:none
対応するクラスファイルを作成します。 (注:-g:none
パラメーターにより、コンパイラーはすべてのデバッグ情報を省略します。デバッグ情報mightは、逆コンパイラーによってより逐語的なバージョンを再構築するために使用されます。特に元の変数名を含む元のコード)
次に、両方のメソッドのバイトコードを見て、
javap -c DecompileExample.class
以下が得られます:
public static void methodA(Java.io.BufferedReader) throws Java.io.IOException;
Code:
0: aconst_null
1: astore_1
2: iconst_0
3: istore_2
4: aload_0
5: invokevirtual #2 // Method Java/io/BufferedReader.readLine:()Ljava/lang/String;
8: dup
9: astore_1
10: ifnull 61
13: getstatic #3 // Field Java/lang/System.out:Ljava/io/PrintStream;
16: new #4 // class Java/lang/StringBuilder
19: dup
20: invokespecial #5 // Method Java/lang/StringBuilder."<init>":()V
23: ldc #6 // String line:
25: invokevirtual #7 // Method Java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
28: aload_1
29: invokevirtual #7 // Method Java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
32: invokevirtual #8 // Method Java/lang/StringBuilder.toString:()Ljava/lang/String;
35: invokevirtual #9 // Method Java/io/PrintStream.println:(Ljava/lang/String;)V
38: iload_2
39: ifne 55
42: aload_1
43: ldc #10 // String |
45: invokestatic #11 // Method Java/util/regex/Pattern.quote:(Ljava/lang/String;)Ljava/lang/String;
48: invokevirtual #12 // Method Java/lang/String.split:(Ljava/lang/String;)[Ljava/lang/String;
51: astore_3
52: goto 4
55: iinc 2, 1
58: goto 4
61: return
そして
public static void methodB(Java.io.BufferedReader) throws Java.io.IOException;
Code:
0: aconst_null
1: astore_1
2: iconst_0
3: istore_2
4: aconst_null
5: astore_3
6: aload_0
7: invokevirtual #2 // Method Java/io/BufferedReader.readLine:()Ljava/lang/String;
10: dup
11: astore_1
12: ifnull 60
15: getstatic #3 // Field Java/lang/System.out:Ljava/io/PrintStream;
18: new #4 // class Java/lang/StringBuilder
21: dup
22: invokespecial #5 // Method Java/lang/StringBuilder."<init>":()V
25: ldc #6 // String line:
27: invokevirtual #7 // Method Java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
30: aload_1
31: invokevirtual #7 // Method Java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
34: invokevirtual #8 // Method Java/lang/StringBuilder.toString:()Ljava/lang/String;
37: invokevirtual #9 // Method Java/io/PrintStream.println:(Ljava/lang/String;)V
40: iload_2
41: ifne 54
44: aload_1
45: ldc #10 // String |
47: invokestatic #11 // Method Java/util/regex/Pattern.quote:(Ljava/lang/String;)Ljava/lang/String;
50: invokevirtual #12 // Method Java/lang/String.split:(Ljava/lang/String;)[Ljava/lang/String;
53: astore_3
54: iinc 2, 1
57: goto 6
60: return
}
(そこにis小さな違いがあります:String[] colArr = null
は
aconst null
astore_3
2番目のバージョンの冒頭。しかし、これは質問で省略したコードの部分に関連する側面の1つです)。
どちらを使用しているかは言及しませんでしたが、 http://jd.benow.ca/ のJD-GUIデコンパイラーは、これを次のように逆コンパイルします。
import Java.io.BufferedReader;
import Java.io.IOException;
import Java.io.PrintStream;
import Java.util.regex.Pattern;
public class DecompileExample
{
public static void methodA(BufferedReader paramBufferedReader)
throws IOException
{
String str = null;
int i = 0;
while ((str = paramBufferedReader.readLine()) != null)
{
System.out.println("line: " + str);
if (i == 0) {
String[] arrayOfString = str.split(Pattern.quote("|"));
} else {
i++;
}
}
}
public static void methodB(BufferedReader paramBufferedReader)
throws IOException
{
String str = null;
int i = 0;
String[] arrayOfString = null;
while ((str = paramBufferedReader.readLine()) != null)
{
System.out.println("line: " + str);
if (i == 0) {
arrayOfString = str.split(Pattern.quote("|"));
}
i++;
}
}
}
両方のケースでコードが同じであることがわかります(少なくともループに関して-コンパイルするために導入しなければならなかった「ダミー変数」に関してもう1つ違いがありますが、これは質問とは無関係です。いわば)。
tl; drメッセージは明確です:
異なるソースコードは、sameバイトコードにコンパイルできます。その結果、sameバイトコードはdifferentソースコードに逆コンパイルできます。ただし、すべての逆コンパイラは、ソースコードの1つのバージョンに対応する必要があります。
(サイドノート:-g:none
なしでコンパイルするとき(つまり、デバッグ情報が保持されるとき)、JD-GUIは最初の1つがwhile
- loopを使用していることをなんとか再構成することに驚いた2番目のものはfor
- loopを使用しましたが、一般的に、デバッグ情報が省略されると、これは単に不可能になります)。
これは基本的にバイトコードの性質によるものです。 Javaバイトコードはアセンブリ言語のようなものなので、for
やwhile
ループなどはなく、単にジャンプ命令goto
があります。したがって、while
ループとfor
ループに違いはない可能性があります。どちらも同様のコードにコンパイルでき、逆コンパイラーは推測するだけです。
for
ループとwhile
ループコードセグメントの両方を、同様のマシンコードに変換できます。その後、逆コンパイラーは逆コンパイル時にtwo possible
シナリオの1つを選択する必要があります。
それがここで起こっていることだと思います。
単に:
compile(A) -> C
compile(B) -> C
したがって、C
が指定されている場合、A
またはB
を選択する推測が必要です。