编译器是否编译了一个简单的三元语句,以便编译一个简单的if else语句?另外,为什么编译器会被设计为以不同方式编译它们?
例如,是这样的:
int a = 169;
int b = 420;
int c;
c = a > b ? 42:69;
编译成同样的东西:
int a = 169;
int b = 420;
int c;
if(a>b) c = 42;
else c = 69;
这个问题不是关于哪个更好或什么时候使用每个问题,所以请不要在答案中包含这个问题。
答案 0 :(得分:6)
首先,这是依赖于实现的。只要在支持Java虚拟机规范的VM上运行时字节码满足Java语言规范,JLS就不会精确指定必须如何编译特定代码段或操作。不同的编译器可以生成与给出的示例不同的字节码,只要它在兼容的JVM上运行时给出相同的结果。
在Java 8的javac
(1.8.0_65)上,条件运算符和if-else的代码不一样。
三元运算符控制将哪个值推送到堆栈,然后无条件地存储堆栈顶部的值。在这种情况下,如果a>b
,则按下42并且代码跳转到istore
,否则推送59。然后,无论价值如何都是istore
d到c
。
在if-else中,实际调用istore
指令的条件控件。
请注意,在这两种情况下,指令都是“比较小于或等于”,跳转到else分支(否则继续if分支)。
下面可以看到各种编译器生成的字节码。您可以使用OpenJDK JDK中提供的javap
工具自己获取它(示例命令行javap -c ClassName
)
javac with ternary:
public static void main(java.lang.String...);
Code:
0: sipush 169
3: istore_1
4: sipush 420
7: istore_2
8: iload_1
9: iload_2
10: if_icmple 18
13: bipush 42
15: goto 20
18: bipush 69
20: istore_3
21: return
使用if-else的javac:
public static void main(java.lang.String...);
Code:
0: sipush 169
3: istore_1
4: sipush 420
7: istore_2
8: iload_1
9: iload_2
10: if_icmple 19
13: bipush 42
15: istore_3
16: goto 22
19: bipush 69
21: istore_3
22: return
}
然而,对于ecj
,代码更奇怪。三元运算符有条件地推送一个或另一个值,然后弹出它以丢弃它(不存储):
Code:
0: sipush 169
3: istore_1
4: sipush 420
7: istore_2
8: iload_1
9: iload_2
10: if_icmple 18
13: bipush 42
15: goto 20
18: bipush 69
20: pop
21: return
带有if-else的 ecj
以某种方式优化了推送/存储,但仍包括奇怪的比较(请注意,对于需要保留的比较没有副作用):
Code:
0: sipush 169
3: istore_1
4: sipush 420
7: istore_2
8: iload_1
9: iload_2
10: if_icmple 13
13: return
当我添加System.out.println(c)
来阻止这个未使用的值丢弃时,我发现这两个语句的结构类似于javac
的结构(三元有条件推送和固定存储,而如果 - 否则做有条件的商店。)
答案 1 :(得分:2)
对于编译器,以下是带有三元表达式的一个语句:
c = a > b ? 42 : 69;
对于编译器,以下是三个不同的语句:
if (a > b) { // statement 1
c = 42; // statement 2
} else {
c = 69; // statement 3
}
每个语句都独立于其他语句编译为字节代码。
分析单独的语句以检测共性,并重新排列代码以生成更好的"字节代码称为优化,完全是可选的。
大多数人在没有优化的情况下进行编译,因为编译时优化与运行时优化相比是无效的,并且编译时优化可以防止(复杂化)调试代码,因为生成的代码将不再与源代码直接相关行号。
示例:如果左侧是myObj.myField
,那么如果NullPointerException
为空,则可以生成myObj
。如果编译器重新排列代码,任何堆栈跟踪都无法分辨哪一行导致异常。
答案 2 :(得分:0)
没有办法大范围回答这个问题。诸如java之类的VM语言将在运行时使用非常复杂的算法优化代码。请参阅What does a just-in-time (JIT) compiler do?。
纯粹基于编译器的语言在某种程度上更容易预测,但是我们需要查看语言,操作系统,编译器等的各个组合。
信任您的编译器和/或虚拟机,以便为您优化这样的简单内容。它可以进行很多更复杂的优化。
答案 3 :(得分:0)
没有人提到OpenJDK ...您可以查看编译Java的代码(从JDK11开始,OpenJDK与OracleJDK合并,这就是为什么您只能查看JDK9的源代码)。这是我对此帖子的简短回答:How exactly does JVM compile ternary operators? Should I be concerned about different api versions? 我查看了JDK9的源代码,据我所知,它是OpenJDK编译器的最新源代码,您无需付费即可查看。
我决定看一下javac的源代码……花了一段时间,但是在他们的搭便车者javac指南的帮助下,我找到了确定行的一行。签出(行914):https://hg.openjdk.java.net/jdk9/jdk9/langtools/file/65bfdabaab9c/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/JavacParser.java 你看到了吗?让我澄清一下,第905-918行这样说:
/** Expression1Rest = ["?" Expression ":" Expression1]
*/
JCExpression term1Rest(JCExpression t) {
if (token.kind == QUES) {
int pos = token.pos;
nextToken();
JCExpression t1 = term();
accept(COLON);
JCExpression t2 = term1();
return F.at(pos).Conditional(t, t1, t2);
} else {
return t;
}
}
该注释告诉我们这是解析三元表达式所使用的内容,如果我们查看返回的内容,它将返回一个条件,其中t
是要求值的表达式,t1
是第一个分支,t2
是第二个分支。为了确定,让我们看一下Conditional
。好像是从Conditional
开始调用F
,如果我们更深入地研究,就会发现TreeMaker
,您可能会问造树者是什么?好吧,它是一个具体的抽象语法树,通常用作解析代码的中间表示(请在此处https://en.wikipedia.org/wiki/Abstract_syntax_tree中进行检查)。无论如何,如果我们查看该文件(https://hg.openjdk.java.net/jdk9/jdk9/langtools/file/65bfdabaab9c/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/TreeMaker.java),我们可以在306-313行看到:
public JCConditional Conditional(JCExpression cond,
JCExpression thenpart,
JCExpression elsepart)
{
JCConditional tree = new JCConditional(cond, thenpart, elsepart);
tree.pos = pos;
return tree;
}
这进一步证实了我们的想法,即三元表达式的编译与if-else语句(否则称为条件语句)完全相同:)我鼓励任何有兴趣的读者参阅旅行者指南({ {3}}和代码,看到一个商业级的编译器如何遵循您在大学的标准编译器课程中学到的许多原理知识,实际上真的很有趣。