而(真); 循环抛出无法到达的代码,当不是在一个空白

我正在用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 ++可能有自己的规则。