而(真); 循环抛出无法到达的代码,当不是在一个空白
我正在用java做一些小程序。 我知道,如果我写的是while(true);
该程序将在此循环中冻结 。 如果代码是这样的:
testing1:
public class While { public static void main(String[] args) { System.out.println("start"); while (true); System.out.println("end"); } }
编译器抛出错误:
Exception in thread "main" java.lang.Error: Unresolved compilation problem: Unreachable code at While.main(While.java:6)
我不知道这个错误存在。 但我知道为什么它被抛出。 当然, 第6行是无法访问的 ,导致编译问题。 然后我testing了这个:
testing2:
public class While { public static void main(String[] args) { System.out.println("start"); a(); b(); } static void a() { while(true); } static void b() { System.out.println("end"); } }
出于某种原因程序正常运行 (控制台打印“开始”,然后冻结)。 编译器无法检查void a()
内部,并发现它不可访问。 可以肯定,我尝试过:
testing3:
public class While { public static void main(String[] args) { System.out.println("start"); a(); System.out.println("end"); } static void a() { while(true); } }
与testing2相同的结果。
经过一番研究,我发现了这个问题 。 所以,如果括号内的代码是一个variables,编译器不会抛出exception 。 这是有道理的,但我不认为这同样适用于voids
。
问:那么,为什么编译器只是在testing1中抛出错误,如果void b()
(Test 2)和System.out.println("end");
(testing3)不可达?
编辑:我试过在C ++testing1:
#include <iostream> using namespace std; int main() { cout << "start" << endl; while(true); cout << "end" << endl; return 0; }
编译器没有抛出任何错误 ,然后我得到了与testing2和testing3相同的结果。所以我想这是一个Java的东西?
语言规范有一个确切的定义什么编译器应视为无法访问的代码,另见https://stackoverflow.com/a/20922409/14955 。
特别是,它不关心一个方法是否完成,而且不会考虑其他方法。
它不会做更多的事情。
但是,您可以使用FindBugs之类的静态代码分析工具来获得“更深层次”的分析(不确定他们是否检测到您描述的模式,而且正如其他人指出的那样,所有generics中的停止问题都无法无论如何要algorithm解决,所以必须在“尽力而为”的合理定义下画线)。
一般来说,不可能绝对确定是否有可达的东西。
为什么? 这相当于停机问题 。
停止问题问:
给定任意计算机程序的描述,决定程序是否结束运行或者继续运行。
这个问题已被certificate是无法解决的。
X代码是否可到达与之前的代码是否会停止相同。
由于这是一个无法解决的问题,所以编译器(使用Java或其他语言)不会很难解决这个问题。 如果碰巧确定它确实无法到达,那么你会得到警告。 如果不是,它可能或不可达。
在Java中,无法访问的代码是一个编译器错误。 所以为了保持兼容性,语言规范确切地定义了编译器应该尝试的“多么困难”。 (根据其他答案,是“不要进入另一个function”)。
在其他语言(如C ++)中,编译器可能会进一步进行优化。 (在内联函数并发现它永不返回时,可能会检测到无法访问的代码。)
无法访问的代码是一个编译时错误,简单地说“这个程序的stream程没有意义; 有些东西永远不会到达“ 。
显然你的testing通过一个无限循环来执行它们的工作方式,但是为什么第一个失败时会出现编译时错误呢?
如果以下至less一个条件成立,while语句可以正常完成:
while语句是可达的,条件expression式不是一个常量expression式(第15.28 ),其值为true。
有一个可访问的break语句退出while语句。
好的,但方法调用(比如a()
)怎么样 – 为什么testing2和3能够成功编译?
- expression式语句可以正常完成,如果它是可达的。
由于方法调用被认为是一个expression式,所以只要没有任何东西阻塞逻辑执行的path,它们总是可以被访问的 。
为了更好地说明这个编译机制背后的一些推理,让我们以if
语句为例。
if(false) System.out.println("Hello!"); // Never executes
以上在编译时是正确的(尽pipe许多IDE肯定会发出呜呜声!)。
Java 1.7规范谈到了这一点 :
这种不同的处理方式的基本原理是允许程序员定义“标志variables”,例如:
static final boolean DEBUG = false;
然后编写代码如:
if (DEBUG) { x=3; }
这个想法是应该可以将DEBUG的值从false更改为true,或者从true更改为false,然后正确编译代码,而不会对程序文本进行其他更改。
而且, 实际上还有一个向后兼容的原因:
这种“有条件编译”的能力对二进制兼容性有重要的影响和关系( §13 )。 如果一组使用这种“标志”variables的类被编译,并且省略了条件代码,那么仅仅分发包含该标志定义的类或接口的新版本是不够的。 因此,标志值的变化与预先存在的二进制文件不能二进制兼容(第13.4.9节 )。 (这种不兼容性还有其他一些原因,例如在switch语句的case标签中使用常量;请参阅§13.4.9 。)
大多数(按照规范),即使不是全部,Java编译器的实现也不会遍历到方法中。 当parsing自己的Java代码时,它将a()
看作是一个MethodInvocationElement
,意思是“该代码调用其他代码。 我真的不在乎,我只是在看语法。 。 从语法上来说,后续代码在调用a()
后属于这种情况。
请记住性能成本。 汇编已经花了相当长的时间。 为了使事情快速起来,Java编译器实际上并没有recursion到方法中; 这需要很长时间(理论上编译器将不得不评估许多代码path)。
为了进一步重申,它的语法驱动是添加一个return;
语句直接在a()
循环之后。 不编译,是吗? 不过从句法上讲,没有它也是有道理的。
答案在于Java语言规范规定的可达性规则。 它首先说
如果语句由于无法执行而无法执行,则会导致编译时错误。
接着
如果满足以下至less一个条件,
while
语句可以正常完成:
while
语句是可达的,条件expression式不是一个常量expression式(第15.28),其值为true
。- 有一个可访问的
break
语句退出while语句。
和
如果expression式语句可以访问,则expression式语句可以正常完成。
在你的第一个例子中,你有一个while循环无法正常完成,因为它有一个条件是一个常量expression式,其值为true
并且在其中没有可达的break
。
在第二个和第三个例子中, expression式语句 (方法调用)是可访问的,因此可以正常完成。
所以我想这是一个Java的东西?
上面的规则是Java的规则。 与其他语言一样,C ++可能有自己的规则。