我在java中做了一些小程序.我知道如果我写while(true);
程序会在这个循环中冻结.如果代码是这样的:
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行无法访问,导致编译问题.然后我测试了这个:
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()
并看到它无法访问.确定我试过了:
public class While { public static void main(String[] args) { System.out.println("start"); a(); System.out.println("end"); } static void a() { while(true); } }
与测试2的结果相同.
经过一番研究,我发现了这个问题.因此,如果括号内的代码是变量,则编译器不会抛出异常.这是有道理的,但我不认为这同样适用voids
.
问:那么,为什么编译器只在测试1中抛出错误,如果void b()
(测试2)和System.out.println("end");
(测试3)无法访问?
编辑:我在C++中尝试过Test 1:
#includeusing namespace std; int main() { cout << "start" << endl; while(true); cout << "end" << endl; return 0; }
编译器没有抛出任何错误,然后我得到了与测试2和测试3相同的结果.所以我想这是一个java的东西?
答案就在订出规则的可达性由Java语言规范.它首先说明
如果由于无法访问语句而无法执行语句,则会发生编译时错误.
然后
一个
while
语句可以正常完成且仅当以下至少有一个为真:
该
while
语句是可到达和条件表达式不是常量表达式(§15.28)与值true
.有一个可到达的
break
语句退出while语句.
和
如果表达式可以访问,则表达式语句可以正常完成.
在第一个示例中,您有一个无法正常完成的while循环,因为它具有一个带有值的常量表达式true
并且在其中无法访问的条件break
.
在第二个和第三个示例中,表达式语句(方法调用)是可访问的,因此可以正常完成.
所以我想这是一个java的东西?
上面的规则是Java的规则.与其他语言一样,C++可能有自己的规则.
无法访问的代码是编译时错误,简单地说'该程序的流程没有意义; 永远不会达到某种东西'.
显然,由于无限循环,您的测试执行它们的工作方式,但为什么第一个会因编译时错误而失败?
如果至少满足下列条件之一,则while语句可以正常完成:
while语句是可访问的,条件表达式不是值为true 的常量表达式(第15.28节).
有一个可到达的break语句退出while语句.
好吧,但是方法调用(例如a()
) - 为什么测试2和3成功编译?
如果表达式可以访问,则表达式语句可以正常完成.
由于方法调用被视为表达式,因此只要在阻塞逻辑执行路径之前没有任何内容,它们将始终可访问.
为了更好地说明这种编译机制背后的一些推理,让我们if
举一个例子.
if(false) System.out.println("Hello!"); // Never executes
以上在编译时是正确的(尽管许多IDE肯定会抱怨!).
Java 1.7规范谈到了这个:
这种不同处理的基本原理是允许程序员定义"标志变量",例如:
static final boolean DEBUG = false;然后编写如下代码:
if (DEBUG) { x=3; }我们的想法是,应该可以将DEBUG的值从false更改为true或从true更改为false,然后正确编译代码而不对程序文本进行其他更改.
此外,实际上还存在向后兼容性原因:
这为"条件编译"能力对于二进制兼容性(一显著的影响和关系§13).如果编译了一组使用这种"flag"变量的类并且省略了条件代码,则稍后仅仅分发包含该标志定义的类或接口的新版本是不够的.因此,对标志值的更改与预先存在的二进制文件不是二进制兼容的(第13.4.9节).(还有其他原因导致这种不兼容性,例如在switch语句中使用case标签中的常量;参见§13.4.9.)
大多数(根据规范),如果不是全部,Java编译器的实现不会遍历方法.解析Java代码本身时,它只看到a()
一个MethodInvocationElement
,意思是"这段代码调用其他代码.我真的不在乎,我只是在看语法..从语法上讲,后续代码在调用之后属于它是有意义的a()
.
请记住性能成本.编译已经花费了相当多的时间.为了保持快速,Java编译器实际上并没有递归到方法中; 这需要很长时间(编译器必须评估很多很多代码路径 - 理论上).
进一步重申它的语法驱动是return;
在你的循环后直接添加一个语句a()
.不编译,是吗?但从语法上讲,没有它就没有意义.
该语言规范有一个确切的定义是什么,编译器应该当作可达代码,也见/sf/ask/17360801/.
特别是,它不关心方法是否完成,并且它不会查看其他方法.
它不会做更多的事情.
但是,您可以使用像FindBugs这样的静态代码分析工具来进行"更深入"的分析(不确定它们是否检测到您描述的模式,或者,正如其他人所指出的那样,所有通用性中的暂停问题都不能无论如何都要通过算法解决,因此必须在"尽力而为"的某种合理定义中划清界限.
通常,不可能绝对肯定地确定某些东西是否可达.
为什么?这相当于停机问题.
暂停问题问:
给定任意计算机程序的描述,确定程序是否完成运行或继续运行.
这个问题已被证明是无法解决的.
X代码是否可访问与说明之前的代码是否将停止相同.
因为它是一个无法解决的问题,所以编译器(用Java或任何其他语言)不会很难解决它.如果碰巧确定它确实无法访问,那么您会收到警告.如果不是,它可能会也可能不会到达.
在Java中,无法访问的代码是编译器错误.因此,为了保持兼容性,语言规范准确地定义了编译器应该尝试的"有多难".(根据其他答案,"不要进入另一个功能".)
在其他语言(例如C++)中,编译器可能会进一步受到优化.(在内联函数并发现它永远不会返回之后,可能会检测到无法访问的代码.)