这些Java字节偏移是如何计算的?

jcm*_*jcm 2 java jvm bytecode

我有以下Java代码:

public int sign(int a) {
  if(a<0) return -1;
  else if (a>0) return 1;
  else return 0;
}
Run Code Online (Sandbox Code Playgroud)

在编译时生成以下字节码:

public int sign(int);
  Code:
     0: iload_1
     1: ifge          6
     4: iconst_m1
     5: ireturn
     6: iload_1
     7: ifle          12
    10: iconst_1
    11: ireturn
    12: iconst_0
    13: ireturn
Run Code Online (Sandbox Code Playgroud)

我想知道如何计算字节偏移计数(第一列),特别是当所有其他指令都是单字节指令时,为什么字节计数ifgeifle指令3字节?

Mar*_*o13 5

正如评论中已经指出的那样:ifgeifle指令有一个额外的偏移量.

为Java虚拟机指令集规范ifge,并ifle包含了相关的提示在这里:

格式

if<cond>
branchbyte1
branchbyte2
Run Code Online (Sandbox Code Playgroud)

这表示该指令有两个附加字节,即"分支字节".这些字节由单个short值组成,以确定偏移量 - 即,当条件满足时,指令指针应该"跳转"多远.


编辑:

这些评论让我很好奇:它offset被定义为一个带符号的 16位值,将跳跃限制在+/- 32k的范围内.这不包括可能方法的整个范围,根据code_length类文件中的方法,该方法最多可包含65535个字节.

所以我创建了一个测试类,看看会发生什么.这个类看起来像这样:

class FarJump
{
    public static void main(String args[])
    {
        call(0, 1);
    }

    public static void call(int x, int y)
    {
        if (x < y)
        {
            y++;
            y++;

            ... (10921 times) ...

            y++;
            y++;
        }
        System.out.println(y);
    }

}
Run Code Online (Sandbox Code Playgroud)

y++行将被转换为一个iinc由3个字节组成的指令.所以得到的字节码是

public static void call(int, int);
    Code:
       0: iload_0
       1: iload_1
       2: if_icmpge     32768
       5: iinc          1, 1
       8: iinc          1, 1

       ...(10921 times) ...

    32762: iinc          1, 1
    32765: iinc          1, 1
    32768: getstatic     #3             // Field java/lang/System.out:Ljava/io/PrintStream;
    32771: iload_1
    32772: invokevirtual #4             // Method java/io/PrintStream.println:(I)V
    32775: return
Run Code Online (Sandbox Code Playgroud)

可以看到它仍然使用一条if_icmpge指令,偏移量为32768(编辑:它是绝对偏移量.相对偏移量是32766.另请参阅此问题)

通过y++在原始代码中添加一个,编译后的代码突然变为

public static void call(int, int);
    Code:
       0: iload_0
       1: iload_1
       2: if_icmplt     10
       5: goto_w        32781
      10: iinc          1, 1
      13: iinc          1, 1
      ....
    32770: iinc          1, 1
    32773: iinc          1, 1
    32776: goto_w        32781
    32781: getstatic     #3             // Field java/lang/System.out:Ljava/io/PrintStream;
    32784: iload_1
    32785: invokevirtual #4             // Method java/io/PrintStream.println:(I)V
    32788: return
Run Code Online (Sandbox Code Playgroud)

因此它将条件从if_icmpgeto转换为if_icmplt,并使用goto_w包含四个分支字节的指令处理远跳,因此可以覆盖(超过)完整的方法范围.

  • @jcm:可能是因为对于大多数分支来说,16位偏移足够了.其含义当然是偶尔的较长分支不能直接表示.如果需要更长的分支,编译器可能会生成一个`goto_w`或者带有相反条件分支的东西. (2认同)