而(真);当没有空时,循环抛出无法访问的代码

时间:2014-06-09 23:04:02

标签: java c++ compiler-construction while-loop

我在java中做了一些小程序。我知道如果我写 while(true);,程序将在此循环中冻结。如果代码是这样的:

测试1:

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行无法访问,导致编译问题。然后我测试了这个:

测试2:

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() 内部,看到它无法访问。确定我试过了:

测试3:

public class While {
    public static void main(String[] args) {
        System.out.println("start");
        a();
        System.out.println("end");
    }
    static void a() {
        while(true);
    }
}

与测试2相同的结果。

经过一些研究后,我发现了 question 因此,如果括号内的代码是变量,则编译器不会抛出异常。这是有道理的,但我不认为这同样适用于 voids

问:那么,如果 void b() (测试2)和 {{,为什么编译器只会在测试1中抛出错误1}} (测试3)无法访问?

编辑:我在C ++中尝试过测试1:

System.out.println("end");

编译器没有抛出任何错误,然后我得到了与测试2和测试3相同的结果。所以我想这是一个java的东西?

4 个答案:

答案 0 :(得分:20)

language spec has an exact definition编译器应将其视为无法访问的代码,另请参阅https://stackoverflow.com/a/20922409/14955

特别是,它不关心方法是否完成,并且它不会查看其他方法。

它不会做更多的事情。

但是,您可以使用像FindBugs这样的静态代码分析工具来获得更深层次的"分析(不确定他们是否检测到你描述的模式,或者,正如其他人所指出的那样,所有普遍性中的暂停问题无论如何都无法通过算法解决,因此必须在某种合理的定义中划清界限。 "尽力而为#34;)。

答案 1 :(得分:12)

一般情况下,无法确定是否可以达到某些内容。

为什么呢?它相当于Halting Problem

暂停问题:

  

给出任意计算机程序的描述,确定程序是否完成运行或继续运行。

这个问题已被证明无法解决。


X代码是否可访问与说明之前的代码是否会停止相同。

因为它是一个无法解决的问题,编译器(用Java或任何其他语言)不会非常努力地解决它。如果碰巧确定它确实无法访问,那么您会收到警告。如果没有,它可能会或可能不会到达。

在Java中,无法访问的代码是编译器错误。因此,为了保持兼容性,语言规范准确地定义了编译器应该尝试的“有多难”。 (根据其他答案,“不要进入另一个功能”。)

在其他语言(例如C ++)中,编译器可能会进一步优化。 (在内联函数并发现它永远不会返回之后,可能会检测到无法访问的代码。)

答案 2 :(得分:12)

Unreachable code是一个编译时错误,只是说'这个程序的流程没有意义;永远不会达到某些东西'

显然,由于无限循环,您的测试会执行它们的工作方式,但为什么第一个会因编译时错误而失败?

  

如果至少有一个,则while语句可以正常完成   以下是真的:

     
      
  • 可以访问while语句,条件表达式不是a   常量表达式(§15.28),值为true。

  •   
  • 有一个可到达的break语句退出while语句。

  •   

好的,但是方法调用(例如a()) - 为什么测试2和3成功编译?

  
      
  • 如果表达式语句可以访问,则表达式语句可以正常完成。
  •   

由于方法调用被视为表达式,因此只要在阻塞逻辑执行路径之前没有任何内容,它们将始终可以访问


为了更好地说明这种编译机制背后的一些推理,让我们举一个if语句,例如。

if(false)
   System.out.println("Hello!"); // Never executes

以上在编译时是正确的(虽然很多IDE肯定会抱怨!)。

The Java 1.7 Specification talks about this

  

这种不同处理的理由是允许程序员   定义"标志变量"如:

static final boolean DEBUG = false;
     

然后编写如下代码:

if (DEBUG) { x=3; }
     

这个想法是应该有可能改变   DEBUG的值从false到true或从true到false然后   正确编译代码而不对程序文本进行其他更改。

此外,实际上也是向后兼容的原因:

  

这种能够有条件地编译"对...有重大影响,   和二进制兼容性(§13)的关系。如果是一组课程   使用这样一个"标志"变量是编译的,条件代码是   省略,以后只分发一个新版本是不够的   包含标志定义的类或接口。一个   因此,更改为标志的值不是二进制兼容的   使用预先存在的二进制文件(§13.4.9)。 (还有其他原因   这种不兼容性,例如在情况下使用常数   switch语句中的标签;见§13.4.9。)


大多数(根据规范),如果不是全部,Java编译器的实现遍历方法。在解析Java代码本身时,它会将a()视为MethodInvocationElement,这意味着'此代码调用其他代码。我真的不在乎,我只是在看语法。' 语法 ,在调用a()之后,后续代码的归属是有意义的。

请记住性能成本。编译已经花费了相当多的时间。为了保持快速,Java编译器实际上没有进入方法;这需要很长时间(编译器必须评估许多很多代码路径 - 理论上)。


进一步重申它的语法驱动是在return;循环之后直接添加a()语句。没有编译,是吗? 语法,但没有它就没有意义。

答案 3 :(得分:6)

答案在于Java Language Specification可达性制定的规则。它首先说明

  

如果语句因无法访问而无法执行,则为编译时错误。

然后

  

while语句可以正常完成iff至少其中一个   以下是真的:

     
      
  • while语句是可以访问的,条件表达式不是值为true的常量表达式(第15.28节)。
  •   
  • 有一个可访问的break语句退出while语句。
  •   

  

如果表达式语句可以访问,则表达式语句可以正常完成。

在第一个示例中,您有一个无法正常完成的while循环,因为它的条件是值为true的常量表达式,并且其中没有可访问的break

在第二个和第三个示例中,expression statement(方法调用)可以访问,因此可以正常完成。


  

所以我想这是一个java的东西?

上述规则是Java的规则。与其他语言一样,C ++可能有自己的规则。