191

我遇到了一种情况,即非 void 方法缺少return语句并且代码仍然可以编译。我知道 while 循环之后的语句是无法访问的(死代码)并且永远不会被执行。但是为什么编译器甚至不警告返回一些东西呢?或者为什么一种语言允许我们拥有一个具有无限循环并且不返回任何内容的非 void 方法?

public int doNotReturnAnything() {
    while(true) {
        //do something
    }
    //no return statement
}

如果我在 while 循环中添加 break 语句(甚至是条件语句),编译器会抱怨臭名昭著的错误:Method does not return a value在 Eclipse 和Not all code paths return a valueVisual Studio 中。

public int doNotReturnAnything() {
    while(true) {
        if(mustReturn) break;
        //do something
    }
    //no return statement
}

Java 和 C# 都是如此。

4

13 回答 13

242

为什么一种语言允许我们拥有一个具有无限循环且不返回任何内容的非 void 方法?

非 void 方法的规则是每个返回的代码路径都必须返回一个值,并且该规则在您的程序中得到满足:返回的代码路径中有零个返回值。规则不是“每个非 void 方法都必须有一个返回的代码路径”。

这使您可以编写存根方法,例如:

IEnumerator IEnumerable.GetEnumerator() 
{ 
    throw new NotImplementedException(); 
}

那是一种非无效的方法。为了满足接口,它必须是非 void 方法。但是将这个实现设为非法似乎很愚蠢,因为它不返回任何东西。

goto由于 a (记住,awhile(true)只是一种更愉快的编写方式goto)而不是 a throw(这是 的另一种形式) ,您的方法具有无法到达的终点goto是不相关的。

为什么编译器甚至不警告返回一些东西?

因为编译器没有很好的证据证明代码是错误的。有人写了while(true),似乎这样做的人知道他们在做什么。

在哪里可以阅读有关 C# 中可达性分析的更多信息?

在此处查看我关于该主题的文章:

ATBG:事实上和法律上的可达性

您还可以考虑阅读 C# 规范。

于 2013-05-28T14:59:59.760 回答
38

Java 编译器足够聪明,可以找到无法访问的代码(while循环后的代码)

并且由于它unreachable因此没有必要在此处添加return语句(while结束后)

有条件的也一样if

public int get() {
   if(someBoolean) {   
     return 10;
   }
   else {
     return 5;
   }
   // there is no need of say, return 11 here;
}

由于布尔条件someBoolean只能计算为trueor false,因此无需return 显式提供after if-else,因为该代码是unreachable,Java 不会抱怨它。

于 2013-05-28T10:36:29.343 回答
17

编译器知道while循环永远不会停止执行,因此方法永远不会完成,因此return不需要语句。

于 2013-05-28T10:35:46.520 回答
14

鉴于您的循环是在一个常量上执行的——编译器知道它是一个无限循环——这意味着该方法无论如何都不会返回。

如果您使用变量 - 编译器将强制执行规则:

这不会编译:

// Define other methods and classes here
public int doNotReturnAnything() {
    var x = true;

    while(x == true) {
        //do something
    }
    //no return statement - won't compile
}
于 2013-05-28T10:37:59.577 回答
11

Java 规范定义了一个名为Unreachable statements. 您的代码中不允许有无法访问的语句(这是编译时错误)。在 while(true); 之后甚至不允许有 return 语句;Java 中的声明。根据while(true);定义,语句使以下语句无法访问,因此您不需要return语句。

请注意,虽然Halting 问题在一般情况下是不可判定的,但 Unreachable Statement 的定义比仅仅停止更严格。它决定了程序绝对不会停止的非常具体的情况。编译器理论上无法检测到所有的无限循环和无法访问的语句,但它必须检测规范中定义的特定情况(例如,while(true)case)

于 2013-05-28T10:39:30.100 回答
7

编译器足够聪明,可以发现您的while循环是无限的。

所以编译器不能为你思考。它无法猜测您为什么编写该代码。同样代表方法的返回值。如果您不对方法的返回值做任何事情,Java 不会抱怨。

所以,回答你的问题:

编译器分析您的代码,并在发现没有执行路径导致函数结束后,它以 OK 结束。

无限循环可能有正当理由。例如,许多应用程序使用无限主循环。另一个例子是一个可以无限期等待请求的网络服务器。

于 2013-05-28T10:41:08.900 回答
7

在类型论中,有一种叫做底部类型的东西,它是所有其他类型的子类(!),用于表示非终止。(异常可以算作一种非终止类型——您不会通过正常路径终止。)

因此,从理论的角度来看,这些非终止语句可以被认为返回了 Bottom 类型的东西,它是 int 的子类型,所以从类型的角度来看,你确实(有点)获得了返回值。完全可以,一种类型可以是包括 int 在内的所有其他类型的子类没有任何意义,因为您实际上从未返回一个类型。

在任何情况下,无论是否通过显式类型理论,编译器(编译器编写者)都认识到在非终止语句之后请求返回值是愚蠢的:不可能有需要该值的情况。(当编译器知道某些内容不会终止但看起来您希望它返回某些内容时,让您的编译器警告您可能会很好。但这最好留给样式检查器,因为也许您需要类型签名出于其他原因(例如子类化),但你真的想要不终止。)

于 2013-05-28T22:54:16.527 回答
6

没有任何情况下函数可以在不返回适当值的情况下结束。因此,编译器没有什么可抱怨的。

于 2013-05-28T11:09:34.830 回答
5

Visual Studio 有智能引擎来检测你是否输入了返回类型,那么它应该在函数/方法中有一个返回语句。

与 PHP 一样,如果您没有返回任何内容,则返回类型为 true。如果没有返回,编译器得到 1。

至此

public int doNotReturnAnything() {
    while(true) {
        //do something
    }
    //no return statement
}

编译器知道 while 语句本身具有无限的性质,所以不要考虑它。如果您在 while 表达式中编写条件,php 编译器将自动为真。

但不是在 VS 的情况下,它会在堆栈中返回一个错误。

于 2013-05-28T10:41:58.183 回答
4

您的 while 循环将永远运行,因此不会出现在 while 之外;它将继续执行。因此,while{} 的外部部分是无法访问的,并且没有写返回的意义。编译器足够智能,可以确定哪些部分可以访问,哪些部分不能访问。

例子:

public int xyz(){
    boolean x=true;

    while(x==true){
        // do something  
    }

    // no return statement
}

上面的代码不会编译,因为可能会出现变量 x 的值在 while 循环体内被修改的情况。所以这使得while循环的外部部分可以访问!因此编译器会抛出一个错误“找不到返回语句”。

编译器不够智能(或者相当懒惰;))来确定 x 的值是否会被修改。希望这能清除一切。

于 2013-05-28T10:54:19.913 回答
4

“为什么编译器甚至不警告返回一些东西?或者为什么一种语言允许我们拥有一个具有无限循环并且不返回任何东西的非 void 方法?”。

此代码在所有其他语言中也有效(可能除了 Haskell!)。因为第一个假设是我们“故意”编写一些代码。

并且在某些情况下,该代码可以完全有效,例如您要将其用作线程;或者如果它返回 a Task<int>,您可以根据返回的 int 值进行一些错误检查 - 这不应该返回。

于 2013-05-28T11:09:25.740 回答
3

我可能错了,但一些调试器允许修改变量。在这里,虽然 x 没有被代码修改并且它将被 JIT 优化,但可能会将 x 修改为 false 并且方法应该返回一些东西(如果 C# 调试器允许这样的东西)。

于 2013-06-18T06:32:37.867 回答
1

Java 案例的细节(可能与 C# 案例非常相似)与 Java 编译器如何确定方法是否能够返回有关。

具体来说,规则是根据JLS 8.4.7,具有返回类型的方法必须不能正常完成,而必须总是突然完成(此处突然通过 return 语句或异常表示)。

如果将方法声明为具有返回类型,则如果方法的主体可以正常完成,则会发生编译时错误。换句话说,具有返回类型的方法必须仅通过使用提供值返回的 return 语句返回;不允许“掉头”

编译器会根据 JLS 14.21 Unreachable Statements 中定义的规则查看是否可以正常终止,因为它还定义了正常完成的规则​​。

值得注意的是,unreachable statements 的规则为具有定义true常量表达式的循环创建了一个特殊情况:

当以下至少一项为真时,while 语句可以正常完成:

  • while 语句是可访问的,并且条件表达式不是值为 true 的常量表达式(第 15.28 节)。

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

因此,如果while语句可以正常完成,那么它下面的 return 语句是必要的,因为代码被认为是可达的,任何while没有可达的 break 语句或常量表达式的循环true都被认为能够正常完成。

这些规则意味着你的while语句带有一个常量真表达式并且没有 a永远不会被认为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 语句可达且条件表达式不是值为 false 的常量表达式时,则 then 语句可达。

  • 如果 then 语句可以正常完成或 else 语句可以正常完成,则 if-then-else 语句可以正常完成。

    • 如果 if-then-else 语句可达且条件表达式不是值为 false 的常量表达式,则 then 语句可达。

    • 如果 if-then-else 语句可达且条件表达式不是值为真的常量表达式,则 else 语句可达。

这种方法与其他控制结构的处理是一致的。但是,为了方便将 if 语句用于“条件编译”目的,实际规则有所不同。

例如,以下语句会导致编译时错误:

while (false) { x=3; }因为该语句x=3;不可访问;但表面上类似的情况:

if (false) { x=3; }不会导致编译时错误。优化编译器可能会意识到该语句x=3;将永远不会被执行,并且可能会选择从生成的类文件中省略该语句的代码,但在x=3;此处指定的技术意义上,该语句不被视为“不可访问”。

这种不同处理的基本原理是允许程序员定义“标志变量”,例如:

static final boolean DEBUG = false;然后编写代码,例如:

if (DEBUG) { x=3; }这个想法是应该可以将 DEBUG 的值从 false 更改为 true 或从 true 更改为 false,然后正确编译代码,而无需对程序文本进行其他更改。

为什么条件 break 语句会导致编译器错误?

正如循环可达性规则中所引用的,如果一个while循环包含一个可达性的break语句,它也可以正常完成。if由于语句的then子句的可达性规则根本不考虑 the 的条件if,因此这样的条件if语句的then子句总是被认为是可达的。

如果break是可达的,那么循环之后的代码也再次被认为是可达的。由于没有可到达的代码导致循环后突然终止,因此该方法被认为能够正常完成,因此编译器将其标记为错误。

于 2013-09-14T00:04:24.620 回答