Java字节码中的if条件取反

11

考虑一个简单的例子

private static String isPositive(int val) {
    if (val > 0) {
        return "yes";
    } else {
        return "no";
    }
}

这里很简单:如果val > 0,则返回yes,否则返回no。 但是,在编译后的字节码中,这个if条件被反转了:

  private static isPositive(I)Ljava/lang/String;
   L0
    LINENUMBER 12 L0
    ILOAD 0
    IFLE L1
   L2
    LINENUMBER 13 L2
    LDC "yes"
    ARETURN
   L1
    LINENUMBER 15 L1
   FRAME SAME
    LDC "no"
    ARETURN

它检查:如果val <= 0,则返回no,否则返回yes

起初,我认为<=的检查更便宜,并且这是某种优化。但是如果我将我的初始代码更改为

if (val <= 0) {
    return "no";
} else {
    return "yes";
}

它仍将在字节码中被反转:

   L0
    LINENUMBER 12 L0
    ILOAD 0
    IFGT L1
   L2
    LINENUMBER 13 L2
    LDC "no"
    ARETURN
   L1
    LINENUMBER 15 L1
   FRAME SAME
    LDC "yes"
    ARETURN

那么,这种行为有原因吗?它可以变得更加直截了当吗?


5
毫无理由认为Java编译器会生成与您所希望的微观细节相对应的字节码。 您为什么关心它是否这样做?(意思是不必在意微观细节,因为Java编译器生成的字节码并不一定完全对应于源代码。) - John Bollinger
3个回答

8

这样做的原因可能是为了让if中的两个代码块在转换后的字节码中以相同的顺序出现。

例如,这段Java代码:

if (val > 0) {
    return "yes";
} else {
    return "no";
}

翻译后大致如下(伪代码):

If val <= 0, then branch to L1
return "yes"
L1:
return "no"  

请注意,在原始的Java代码中,通过检查if条件来确定是否应该运行第一个代码块,而在翻译后的字节码中,检查是为了确定是否需要采取分支(跳过第一个代码块)。因此,它需要检查一个互补的条件。

能不能简单点说?

当然,也可以保留条件,但这样你需要颠倒两个代码块的顺序:
If val > 0, then branch to L1
return "no"
L1:
return "yes"  

我不会说这个版本比之前的更“直接”,虽然。

无论如何,为什么要改变它呢?两个版本都应该很好。


谢谢,这确实有道理!明天我会尝试更复杂的if条件语句,但我的观点是,我可能会期望将此条件按原样翻译,而不是反转。并不是说我想改变它,这个条件完全没问题,只是在尝试字节码操作时注意到了它。 - esin88

5

实际上,反转条件是最直接的编译策略。您可以编写与该模式匹配的Java代码。

if(condition_fullfilled) {
    somecode
}

这将被编译成与该模式匹配的字节码

  goto_if_condition_not_fullfilled A_LABEL
  compiled_somecode
A_LABEL:

由于条件分支指的是“何时跳过”条件代码,其条件必须与您的源代码相反,即“何时执行”条件代码。
上面的示例没有else部分,说明编译if语句并使用具有与您的源代码相同条件的条件分支指令没有简单的方法。虽然可以实现,但需要多个分支指令。
对于没有else的if语句,这种直接的编译策略可以轻松扩展到处理else。当存在else子句时,没有理由改变策略,例如交换语句的顺序。
请注意,在您的情况下,两个分支都以return语句结束,没有区别。
if (val > 0) {
    return "yes";
} else {
    return "no";
}

并且

if (val > 0) {
    return "yes";
}
return "no";

anyway.


0

可能的原因是字节码使用的操作数栈来执行其语句。

首先进行比较,然后将1、0或-1推送到操作数栈上。接下来,根据操作数栈上的值是否大于、小于或等于零执行分支。

JavaCodeToByteCode#conditionals中有一个很好的图形细节,以及一个详细的Instruction For Branching


1
你引用的段落适用于 longfloatdouble 比较,当使用 ...cmp 指令时。除此之外,我不明白操作数栈如何回答这个问题。 - Holger

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接