而(真);当不在 void 中时,循环会抛出无法访问的代码

Posted

技术标签:

【中文标题】而(真);当不在 void 中时,循环会抛出无法访问的代码【英文标题】:while(true); loop throws Unreachable code when isn't in a void 【发布时间】:2014-07-30 14:10:43 【问题描述】:

我正在用 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)和 System.out.println("end");(测试 3)无法访问?

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

#include <iostream>

using namespace std;

int main()

    cout << "start" << endl;
    while(true);
    cout << "end" << endl;
    return 0;

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

【问题讨论】:

Java 编译器不会“查看”其他方法,因为它对可达/不可达代码进行 原始 静态分析。 (这样做会给虚拟方法/多态性和单独的编译单元带来许多复杂性。) 问得很好。 @user2864740 是正确的。在整个程序中寻找可访问/不可访问的代码相当于解决停机问题(如果有记忆的话),所以它不能像大多数人想要的那样完成。 @user3580294 在整个程序中提供完整且完全准确的可达性分析将等同于解决停机问题。然而,有很多方法可以查看并返回不完整但有用的结果,使用更高级的启发式。 (一般来说,每当 任何人 说给定的编译器任务等同于解决停机问题时,他们就错了。“停机问题”应该是您的即时“否”触发短语之一.) @Leushenko 好吧,我有点含糊其辞,但我不认为我说过这是一项不可能完成的任务。我只是说“它不能像大多数人想要的那样完成”,这为编译器留下了足够的空间来获取一些信息。感谢您的澄清,尤其是斜体部分。 【参考方案1】:

Unreachable code 是一个编译时错误,简单地说'这个程序的流程没有意义;永远都达不到的东西'

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

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

while 语句可达,条件表达式不是 值为 true 的常量表达式 (§15.28)。

有一个可达的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 编译器实际上并没有递归到方法中。这需要很长时间(理论上,编译器必须评估很多很多代码路径)。


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

【讨论】:

【参考方案2】:

一般来说,不可能绝对确定某物是否可达。

为什么?它相当于Halting Problem。

停机问题问:

给定一个任意计算机程序的描述,决定程序是完成运行还是永远继续运行。

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


X 段代码是否可达与判断它之前的代码是否会停止是一样的。

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

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

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

【讨论】:

“因为它是一个无法解决的问题,所以编译器不会很努力地解决它”。好吧,如果计算成本是唯一的问题,它可能会更加努力。但它的双手受语言规范的约束。最多,它可以发出一些警告(但不是错误)。 JLS 规则在这方面比停止问题的不可判定性更重要。编译器可以进行某种程度的过程间分析,但 JLS 排除了这一点。 @Thilo 问题变成了编译器应该在什么时候停止尝试解决它。我想在 Java 的情况下,语言规范已经决定了。 对。并且规范必须明确这一点,因为我们不想要不兼容的编译器。 @Thilo 哦,我没有意识到无法访问的代码是Java 中的编译器错误。这只是我使用的其他语言(C++/C#)中的一个警告。是的,在这种情况下,我同意需要指定它。【参考方案3】:

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

如果由于无法访问而无法执行语句,则这是编译时错误。

然后

while 语句可以正常完成,但前提是至少满足以下条件之一 以下是正确的:

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

表达式语句只要可达,就可以正常完成。

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

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


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

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

【讨论】:

你的答案中唯一真正解决他问题的部分是最后一个陈述。 @Qix 是否完全解决了这个问题?还是缺少什​​么? 如果您删除了不相关的部分,这可能被视为仅链接的答案。只是把它扔在那里。 @Qix 问题So, why do the compiler just throws me the error at Test 1, if void b() (Test 2) and System.out.println("end"); (Test 3) isn't reachable? 链接和引号解释了为什么。最后一段解释了它如何应用于测试 1 中的while,最后一段解释了它如何应用于测试 2 和 3。我错过了什么吗?什么是无关紧要的?【参考方案4】:

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

特别是,它不关心一个方法是否完成,也不查看其他方法的内部。

它不会做更多的事情。

但是,您可以使用 FindBugs 等静态代码分析工具来进行“更深入”的分析(但不确定它们是否检测到您所描述的模式,并且正如其他人所指出的那样,无论如何,所有一般性都无法通过算法解决,因此必须在“尽力而为”的某个合理定义上划清界限)。

【讨论】:

以上是关于而(真);当不在 void 中时,循环会抛出无法访问的代码的主要内容,如果未能解决你的问题,请参考以下文章

为啥我的存储过程在包含在事务块中时会抛出错误?

当整数除以零时,msvc 6 会抛出啥?

当它们不在服务器中时,如何删除商店中的记录

为啥 VsPerfAspNetCmd 会抛出错误“值不在预期范围内”?

BluetoothAdapter.getDefaultAdapter() 在不在 Activity 中时抛出 RuntimeException

当 Object 不在 DOM 中时,如何修复无法读取 React 钩子中 null 的 querySelector