web-dev-qa-db-ja.com

なぜこのループが変更されたのですか?

私はちょうど私のクラスのこの逆コンパイルされたクラスファイルに遭遇しました:

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ループが提供する利点は何かを知りたいだけです。
このようなコード最適化のカテゴリは何ですか?

39
KumarAnkit

この場合、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

}

forwhileの使用法に関連する違いはありません。

45
dgebert

他の人がすでに指摘しているように:デコンパイラーは(通常)同じバイトコードをもたらす異なるソースコードを区別できません。

残念ながら、メソッドの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を使用しましたが、一般的に、デバッグ情報が省略されると、これは単に不可能になります)。

15
Marco13

これは基本的にバイトコードの性質によるものです。 Javaバイトコードはアセンブリ言語のようなものなので、forwhileループなどはなく、単にジャンプ命令gotoがあります。したがって、whileループとforループに違いはない可能性があります。どちらも同様のコードにコンパイルでき、逆コンパイラーは推測するだけです。

forループとwhileループコードセグメントの両方を、同様のマシンコードに変換できます。その後、逆コンパイラーは逆コンパイル時にtwo possibleシナリオの1つを選択する必要があります。

それがここで起こっていることだと思います。

単に:

compile(A) -> C

compile(B) -> C

したがって、Cが指定されている場合、AまたはBを選択する推測が必要です。

5
prime