反编译以下for-each循环的.class文件会产生有趣的结果。
源-Main.java:
public class Main {
public static void main(String[] args) {
String[] names = new String[3];
int var3 = 3;
for (String name : names) {
System.out.println(name);
}
}
}
结果-Main.class:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
public class Main {
public Main() {
}
public static void main(String[] args) {
String[] names = new String[3];
int var3 = true;
String[] var3 = names;
int var4 = names.length;
for(int var5 = 0; var5 < var4; ++var5) {
String name = var3[var5];
System.out.println(name);
}
}
}
该文件已使用IntelliJ IDEA反编译。
true
分配给未使用的int
?var3
变量?这是代表反编译器的错误吗?
答案 0 :(得分:3)
在字节码级别上,没有正式的局部变量声明,至少不是从源代码中知道的方式。方法声明了同时存在的最大局部变量或为它们保留的“插槽”的最大数量。当为局部变量分配了一个实际值(通过“ slot”索引)后,该局部变量就生效了,并且至少存在于该值的最后一次读取中。
通过这些操作,无法识别变量的作用域何时结束,或者具有相异作用域的两个变量是否共享一个插槽(与对同一变量的多次分配相比)。好吧,如果它们具有完全不兼容的类型,则它们的分配会给出提示。
为了帮助调试,有一个可选的code属性,它提供有关已声明的局部变量及其范围的提示,但这不是必须完整的操作,也不会影响JVM执行字节码的方式。但是在这里,似乎该属性存在并且已被反编译器使用。
当我用javac -g
编译示例代码时,我得到了
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: (0x0009) ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=7, args_size=1
0: iconst_3
1: anewarray #2 // class java/lang/String
4: astore_1
5: iconst_3
6: istore_2
7: aload_1
8: astore_3
9: aload_3
10: arraylength
11: istore 4
13: iconst_0
14: istore 5
16: iload 5
18: iload 4
20: if_icmpge 43
23: aload_3
24: iload 5
26: aaload
27: astore 6
29: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
32: aload 6
34: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
37: iinc 5, 1
40: goto 16
43: return
LocalVariableTable:
Start Length Slot Name Signature
29 8 6 name Ljava/lang/String;
0 44 0 args [Ljava/lang/String;
5 39 1 names [Ljava/lang/String;
7 37 2 var3 I
已将声明的变量args
(方法参数)names
,var3
和name
分配给变量索引0
,{{1 }},1
和2
的顺序。
有些合成变量没有声明,
6
上保存对循环进行迭代的数组的引用3
处以保留数组长度4
处以保存5
索引变量,该变量将在循环中递增反编译器似乎有一个简单的策略来处理int
中未包含的变量。它生成一个由前缀LocalVariableTable
和堆栈帧内的索引组成的名称。因此,它为上述合成变量生成了名称"var"
,var3
和var4
,并且不在乎这些生成的名称与显式声明的名称之间是否存在名称冲突,即var5
。
现在,不清楚为什么反编译器会为var3
变量生成true
的赋值,但是它有助于知道Java字节码中没有专门的int
处理指令,但是boolean
值的处理方式与boolean
值相同。它需要适当的元信息,例如变量声明,以了解何时应将值解释为int
值。也许,上述名称冲突导致反编译器随后混淆了变量类型,最终认为值类型不是boolean
,然后退回去将其视为int
。但这只是一个猜测;也可能有一个完全不相关的错误。