我已经在 (Java) 字节码上工作了一段时间,但是,我从来没有想过要问为什么输入一些指令?我知道在 ADD 操作中,我们需要区分整数加法和 FP 加法(这就是我们有 IADD 和 FADD 的原因)。但是,为什么要区分ISTORE和FSTORE呢?它们都涉及完全相同的操作,即从堆栈移动 32 位到局部变量位置?
我能想到的唯一答案是类型安全,以防止出现这种情况:(ILOAD、ILOAD、FADD)。但是,我相信类型安全已经在 Java 语言级别得到实现。好吧,Class 文件格式并没有直接与 Java 结合,那么这是一种为不支持它的语言强制类型安全的方法吗?任何想法?谢谢。
编辑: 跟进 Reedy 的回答。我写了这个最小的程序:
public static void main(String args[])
{
int x = 1;
}
编译为:
iconst_1
istore_1
return
使用字节码编辑器,我更改了第二条指令:
iconst_1
fstore_1
return
它返回了一个 java.lang.VerifyError: Expecting to find float on stack。
我想知道,如果堆栈上没有关于类型的信息,只有位,那么 FSTORE 指令如何知道它处理的是 int 而不是 float?
注意:我无法为这个问题找到更好的标题。随时改进它。
最佳答案
输入这些指令是为了确保程序是类型安全的。当加载一个类时,虚拟机对字节码执行验证以确保,例如, float 不会作为参数传递给需要整数的方法。这种静态验证要求验证者可以确定任何给定执行路径的堆栈上值的类型和数量。加载和存储指令需要类型标记,因为堆栈帧中的局部变量没有类型(即,您可以先存储到局部变量,然后再存储到相同位置)。指令上的类型标签允许验证者知道每个局部变量中存储的值是什么类型。
validator 查看方法中的每个操作码,并在执行每个操作码后跟踪堆栈和局部变量中的类型。你是对的,这是另一种形式的类型检查,并且确实重复了 java 编译器完成的一些检查。验证步骤可防止加载任何会导致 VM 执行非法指令的代码,并确保 Java 平台的安全属性,而不会导致在每次操作之前检查类型的大量运行时间损失。每次执行方法时,每个操作码的运行时类型检查都会影响性能,但静态验证仅在加载类时进行一次。
案例一:
Instruction Verification Stack Types Local Variable Types
----------------------- --------------- ---------------------- -----------------------
<method entry> OK [] 1: none
iconst_1 OK [int] 1: none
istore_1 OK [] 1: int
return OK [] 1: int
案例二:
Instruction Verification Stack Types Local Variable Types
----------------------- --------------- ---------------------- -----------------------
<method entry> OK [] 1: none
iconst_1 OK [int] 1: none
fstore_1 Error: Expecting to find float on stack
给出错误是因为验证者知道 fstore_1 期望栈上有一个 float ,但执行前面指令的结果在栈上留下了一个 int。
这种验证是在不执行操作码的情况下完成的,而是通过查看指令的类型来完成的,就像 java 编译器在您编写 (Integer)"abcd"
时给出错误一样。编译器不必运行程序就知道 "abcd"
是一个字符串并且不能转换为 Integer
。
关于java - 字节码中的类型,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/2637879/