非 void 方法中缺少 return 语句编译
Posted
技术标签:
【中文标题】非 void 方法中缺少 return 语句编译【英文标题】:Missing return statement in a non-void method compiles 【发布时间】:2013-05-23 07:02:31 【问题描述】:我遇到了一种情况,即 non-void 方法 缺少 return 语句并且代码仍然可以编译。 我知道 while 循环之后的语句是 unreachable (死代码)并且永远不会被执行。但是为什么编译器甚至不警告返回一些东西呢?或者为什么一种语言允许我们拥有一个具有无限循环并且不返回任何内容的非 void 方法?
public int doNotReturnAnything()
while(true)
//do something
//no return statement
如果我在 while 循环中添加 break 语句(甚至是条件语句),编译器会抱怨臭名昭著的错误:Eclipse 中的 Method does not return a value
和 Visual Studio 中的 Not all code paths return a value
。
public int doNotReturnAnything()
while(true)
if(mustReturn) break;
//do something
//no return statement
Java 和 C# 都是如此。
【问题讨论】:
好问题。我会对此原因感兴趣。 一个猜测:它是一个无限循环,所以返回控制流无关紧要? “为什么一种语言允许我们拥有一个具有无限循环且不返回任何内容的非 void 方法?” 对于Java,你可以在this answer找到一个很好的解释。 就像其他人解释的那样,这是因为编译器足够聪明,知道循环是无限的。请注意,尽管编译器不仅允许缺少返回,它强制它,因为它知道循环无法访问之后的任何内容。至少在 Netbeans 中,如果在循环之后有 anything,它实际上会抱怨unreachable statement
。
【参考方案1】:
编译器知道while
循环永远不会停止执行,因此该方法永远不会完成,因此不需要return
语句。
【讨论】:
【参考方案2】:Java 编译器足够聪明,可以找到无法访问的代码(while
循环之后的代码)
由于它无法访问,没有意义在此处添加return
语句(在while
结束后)
条件if
也是如此
public int get()
if(someBoolean)
return 10;
else
return 5;
// there is no need of say, return 11 here;
由于布尔条件someBoolean
只能计算为true
或false
,因此无需在if-else
之后明确 提供return
,因为该代码是无法访问,Java 不会抱怨。
【讨论】:
我认为这并不能真正解决问题。这回答了为什么您在无法访问的代码中不需要return
语句,但与您在 OP 的代码中不需要 any return
语句的原因无关。
如果 Java 编译器足够聪明,可以找到无法访问的代码(while 循环之后的代码),那么为什么下面这些代码会编译,它们都有无法访问的代码,但带有 if 的方法需要返回语句,但方法有一阵子没有。 public int doNotReturnAnything()
if(true)
System.exit(1);
return 11;
public int doNotReturnAnything()
while(true)
while(true)
System.exit(1);
return 11;// Compiler error: unreachable code
pan@9876
@SandeepPoonia:因为编译器不知道System.exit(1)
会杀死程序。它只能检测某些类型的无法访问的代码。
@Daniel Hilgarth:好的编译器不知道System.exit(1)
会杀死程序,我们可以使用任何return statement
,现在编译器可以识别return statements
。并且行为是相同的,if condition
需要返回,while
不需要。
不使用while(true)
等特殊情况的不可达部分的良好演示【参考方案3】:
鉴于您的循环是在一个常量上执行的 - 编译器知道这是一个无限循环 - 这意味着该方法无论如何都不会返回。
如果您使用变量 - 编译器将强制执行规则:
这不会编译:
// Define other methods and classes here
public int doNotReturnAnything()
var x = true;
while(x == true)
//do something
//no return statement - won't compile
【讨论】:
但是如果“做某事”不涉及以任何方式修改 x 怎么办.. 编译器不是很聪明地想出来吗?流浪汉:( 不——看起来不像。 @Lews 如果你有一个方法被标记为返回 int,但实际上没有返回,那么你应该很高兴编译器标记了这个,所以你可以将该方法标记为 void 或修复如果你不打算这样做的话。 @MikeFHay 是的,没有争议。 我可能错了,但有些调试器允许修改变量。在这里,虽然 x 没有被代码修改,它将被 JIT 优化,但可能会将 x 修改为 false,并且方法应该返回一些东西(如果 C# 调试器允许这样的东西)。【参考方案4】:Java 规范定义了一个名为Unreachable statements
的概念。您的代码中不允许有无法访问的语句(这是编译时错误)。在 while(true); 之后甚至不允许有 return 语句; Java 中的声明。 while(true);
语句根据定义使以下语句无法访问,因此您不需要 return
语句。
请注意,虽然Halting problem 在一般情况下是不可判定的,但 Unreachable Statement 的定义比仅仅停止更严格。它决定了程序绝对不会停止的非常具体的情况。编译器理论上无法检测到所有无限循环和无法访问的语句,但它必须检测规范中定义的特定情况(例如,while(true)
情况)
【讨论】:
【参考方案5】:编译器足够聪明,可以发现您的 while
循环是无限的。
所以编译器无法为你思考。它无法猜测为什么您编写了该代码。同样代表方法的返回值。如果您不对方法的返回值做任何事情,Java 不会抱怨。
所以,回答你的问题:
编译器分析您的代码,并在发现没有执行路径导致函数结束后,它以 OK 结束。
无限循环可能有合理的原因。例如,许多应用程序使用无限主循环。另一个例子是一个可以无限期等待请求的网络服务器。
【讨论】:
【参考方案6】:Visual Studio 有智能引擎来检测你是否输入了返回类型,那么它应该在函数/方法中有一个返回语句。
与 php 一样,如果您没有返回任何内容,则返回类型为 true。如果没有返回任何内容,编译器会得到 1。
截至目前
public int doNotReturnAnything()
while(true)
//do something
//no return statement
编译器知道 while 语句本身具有无限的性质,所以不要考虑它。如果你在while的表达式中写一个条件,php编译器会自动为真。
但不是在 VS 的情况下,它会在堆栈中返回一个错误。
【讨论】:
【参考方案7】:您的 while 循环将永远运行,因此不会出现在 while 之外;它将继续执行。因此,while 的外部部分是无法访问的,并且没有写返回的意义。编译器足够智能,可以确定哪些部分可以访问,哪些部分不能访问。
例子:
public int xyz()
boolean x=true;
while(x==true)
// do something
// no return statement
上面的代码不会编译,因为在while循环体内可能会修改变量x的值。所以这使得while循环的外部部分可以访问!因此编译器会抛出一个错误“找不到返回语句”。
编译器不够聪明(或者说是懒惰;))无法确定 x 的值是否会被修改。希望这能清除一切。
【讨论】:
在这种情况下,实际上并不是编译器足够聪明的问题。在 C# 2.0 中,编译器足够聪明地知道int x = 1; while(x * 0 == 0) ...
是一个无限循环,但 规范 说编译器应该只在循环表达式为 constant 时进行控制流推导i>,并且规范将常量表达式定义为不包含变量。因此编译器太聪明了。在 C# 3 中,我使编译器与规范相匹配,从那时起,它就故意不那么聪明地处理这些类型的表达式。【参考方案8】:
“为什么编译器甚至不警告返回一些东西?或者为什么一种语言允许我们拥有一个具有无限循环并且不返回任何东西的非 void 方法?”。
此代码在所有其他语言中也有效(可能除了 Haskell!)。因为第一个假设是我们“故意”编写一些代码。
在某些情况下,此代码可能完全有效,例如您要将其用作线程;或者如果它返回 Task<int>
,您可以根据返回的 int 值进行一些错误检查 - 不应返回。
【讨论】:
【参考方案9】:没有任何情况下函数可以在没有返回适当值的情况下结束。因此,编译器没有什么可抱怨的。
【讨论】:
【参考方案10】:为什么一种语言允许我们拥有一个具有无限循环且不返回任何内容的非 void 方法?
非 void 方法的规则是每个返回的代码路径都必须返回一个值,并且该规则在您的程序中得到满足:返回的代码路径中有零个返回值。规则不是“每个非 void 方法都必须有一个返回的代码路径”。
这使您可以编写存根方法,例如:
IEnumerator IEnumerable.GetEnumerator()
throw new NotImplementedException();
这是一个非 void 方法。为了满足接口,它必须是一个非空方法。但是将这个实现设为非法似乎很愚蠢,因为它不返回任何东西。
由于goto
(请记住,while(true)
只是写goto
的一种更愉快的方式)而不是throw
(这是@987654327 的另一种形式),您的方法有一个无法到达的终点@) 不相关。
为什么编译器甚至不警告返回一些东西?
因为编译器没有很好的证据证明代码是错误的。有人写了while(true)
,这样做的人似乎知道他们在做什么。
在哪里可以阅读有关 C# 中可达性分析的更多信息?
在此处查看我关于该主题的文章:
ATBG: de facto and de jure reachability
您还可以考虑阅读 C# 规范。
【讨论】:
那么为什么这段代码会出现编译时错误:public int doNotReturnAnything()
boolean flag = true;
while (flag)
//Do something
//no return
这段代码也没有断点。那么现在编译器如何知道代码是错误的。
@SandeepPoonia:您可能想在这里阅读其他答案...编译器只能检测某些条件。
@SandeepPoonia:布尔标志可以在运行时更改,因为它不是常量,因此循环不一定是无限的。
@SandeepPoonia:在 C# 中,规范说 if
、while
、for
、switch
等,在 constants 上运行的分支构造被处理作为编译器的无条件分支。 constant expression 的确切定义在规范中。您的问题的答案在规范中;我的建议是你阅读它。
Every code path that returns must return a value
最佳答案。清楚地解决了这两个问题。谢谢【参考方案11】:
在类型理论中,有一种称为 底部类型 的东西,它是所有其他类型 (!) 的子类,用于指示非终止。 (异常可以算作一种非终止类型——您不会通过正常路径终止。)
所以从理论的角度来看,这些非终止语句可以被认为返回底部类型的东西,它是 int 的子类型,所以从类型的角度来看,你确实(有点)获得了返回值.一种类型可以是包括 int 在内的所有其他类型的子类没有任何意义,这是完全可以的,因为您实际上永远不会返回一个。
在任何情况下,无论是否通过显式类型理论,编译器(编译器编写者)都认识到在非终止语句之后请求返回值是愚蠢的:您不可能需要该值。 (当编译器知道某些内容不会终止但看起来您希望它返回某些内容时,让您的编译器警告您可能会很好。但这最好留给样式检查器,因为也许您需要类型签名出于其他原因(例如子类化),但您确实希望不终止。)
【讨论】:
【参考方案12】:我可能错了,但有些调试器允许修改变量。在这里,虽然 x 没有被代码修改并且它将被 JIT 优化,但可能会将 x 修改为 false 并且方法应该返回一些东西(如果 C# 调试器允许这样的东西)。
【讨论】:
【参考方案13】:Java 案例的细节(可能与 C# 案例非常相似)与 Java 编译器如何确定方法是否能够返回有关。
具体来说,规则是具有返回类型的方法不能正常完成,而必须总是突然完成(这里突然通过return语句表示或例外)每个JLS 8.4.7。
如果一个方法被声明为有返回类型,那么编译时 如果方法的主体可以正常完成,则会发生错误。 换句话说,具有返回类型的方法必须仅通过使用返回 提供返回值的 return 语句; 不允许 “从身体的末端掉下来”。
编译器会根据JLS 14.21 Unreachable Statements 中定义的规则查看正常终止是否可能,因为它还定义了正常完成的规则。
值得注意的是,无法访问语句的规则为具有定义的true
常量表达式的循环创建了一个特殊情况:
当至少满足以下条件之一时,while 语句才能正常完成 以下是正确的:
while 语句可达,条件表达式不是 值为 true 的常量表达式(第 15.28 节)。
有一个可达的break语句退出while语句。
所以如果while
语句可以正常完成,那么它下面的return 语句是必要的,因为代码被认为是可达的,并且任何while
循环没有可达的break 语句或常量@ 987654326@表达式被认为能够正常完成。
这些规则意味着您的 while
语句具有常量 true 表达式且没有 break
不会被视为正常完成,因此它下面的任何代码都不会被视为可以到达。方法的结尾在循环下方,由于循环下方的所有内容都不可达,因此方法的结尾也是如此,因此该方法不可能正常完成(这是编译器寻找的)。
另一方面,if
语句没有关于提供给循环的常量表达式的特殊豁免。
比较:
// I have a compiler error!
public boolean testReturn()
final boolean condition = true;
if (condition) return true;
与:
// I compile just fine!
public boolean testReturn()
final boolean condition = true;
while (condition)
return true;
区别的原因很有趣,并且是由于希望允许不会导致编译器错误的条件编译标志(来自 JLS):
人们可能期望 if 语句在下面处理 方式:
if-then 语句可以正常完成当且仅当且仅满足以下条件之一 以下是正确的:
if-then 语句可达,条件表达式不可达 一个值为真的常量表达式。
then-语句可以正常完成。
当 if-then 语句可达时 then 语句可达 并且条件表达式不是其值的常量表达式 是假的。
if-then-else 语句可以正常完成,如果 then-statement 可以正常完成或者else语句可以正常完成。
当 if-then-else 语句为 可达且条件表达式不是常量表达式 其值为假。
如果 if-then-else 语句为 可达且条件表达式不是常量表达式 其值为真。
这种方法将与其他控件的处理一致 结构。但是,为了允许使用 if 语句 方便地用于“条件编译”目的,实际规则 不同。
例如,以下语句导致编译时 错误:
while (false) x=3;
因为语句x=3;
不可达; 但表面上类似的情况:
if (false) x=3;
不会导致编译时错误。一个 优化编译器可能会意识到语句x=3;
永远不会 执行,并且可以选择从 生成了class文件,但是x=3;
这个语句不被认为是 此处指定的技术意义上的“无法访问”。这种不同处理的基本原理是允许程序员 定义“标志变量”,例如:
static final boolean DEBUG = false;
然后写代码如:
if (DEBUG) x=3;
这个想法应该是可以改变的 DEBUG 的值从 false 到 true 或者从 true 到 false 然后 正确编译代码,无需对程序文本进行其他更改。
为什么条件break语句会导致编译错误?
正如循环可达性规则中所引用的,如果一个while循环包含一个可达性的break语句,它也可以正常完成。由于if
语句的then 子句的可达性规则根本没有考虑if
的条件,所以这样的条件if
语句的then 子句始终被认为是可达的。
如果break
是可达的,那么循环之后的代码也再次被认为是可达的。由于没有可到达的代码导致循环后突然终止,因此该方法被认为能够正常完成,因此编译器将其标记为错误。
【讨论】:
以上是关于非 void 方法中缺少 return 语句编译的主要内容,如果未能解决你的问题,请参考以下文章