在非 void 方法编译中缺少返回语句

我遇到了这样一种情况: 非空穴法非空穴法缺少 返回语句,而代码仍在编译。 我知道 while 循环后面的语句是 联系不上(死代码) ,永远不会被执行。但是为什么编译器甚至不警告返回一些内容呢?或者,为什么一种语言允许我们有一个非 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 # 都是如此。

12090 次浏览

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

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只能求值为 truefalse,因此没有必要在 if-else之后提供一个 return 明确地,因为该代码是 联系不上,而且 Java 不会抱怨它。

假设你的循环是在一个常量上执行的——编译器知道这是一个无限循环——这意味着无论如何方法永远不会返回。

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

这无法编译:

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


while(x == true) {
//do something
}
//no return statement - won't compile
}

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

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

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

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

回答你的问题:

编译器会分析你的代码,在发现没有执行路径会导致它用 OK 结束的函数的结尾掉下来之后。

可能存在无限循环的合理原因。例如,许多应用程序使用无限主循环。另一个例子是 Web 服务器,它可能无限期地等待请求。

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

在 PHP 中,如果没有返回任何内容,则返回类型为 true。如果没有返回任何值,编译器将得到1。

从现在开始

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

编译器知道,虽然语句本身具有无穷的性质,所以不要考虑它。如果你用 while 的表达式写一个条件,php 编译器会自动得到 true。

但是在 VS 的情况下,它将返回堆栈中的错误。

您的 while 循环将永远运行,因此不会出现在 while 之外; 它将继续执行。因此,while {}的外部部分是不可达的,并且在写回车与否方面没有任何意义。编译器足够聪明,能够分辨出哪些部分是可到达的,哪些部分是不可到达的。

例如:

public int xyz(){
boolean x=true;


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


// no return statement
}

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

编译器不够聪明(或者说不够懒惰;) ,无法判断 x 的值是否会被修改。希望这能解决所有问题。

”为什么编译器甚至不警告返回某些内容?或者,为什么一种语言允许我们有一个非 void 方法,它有一个无限循环,却不返回任何内容?”.

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

在某些情况下,这段代码可能是完全有效的,比如,如果你将它用作一个线程; 或者,如果它返回一个 Task<int>,你可以根据返回的 int 值进行一些错误检查——它不应该被返回。

在任何情况下,函数都会返回一个适当的值。因此,编译器没有什么可抱怨的。

为什么一种语言允许我们有一个非 void 方法,它有一个无限循环却不返回任何东西?

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

这使您能够编写诸如下面这样的存根方法:

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

这是一个非无效的方法。为了满足接口的要求,已经是一个非 void 方法。但是将此实现定为非法似乎有些愚蠢,因为它不返回任何内容。

因为 goto(记住,while(true)只是写 goto的一种更愉快的方式)而不是 throw(goto的另一种形式) ,所以你的方法有一个不可到达的终点是不相关的。

为什么编译器甚至不警告返回某些内容?

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

我在哪里可以阅读更多关于 C # 的可达性分析?

点击这里查看我关于这个主题的文章:

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

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

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

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

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

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

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

具体来说,规则是具有返回类型的方法必须不能返回 完全正常,而必须始终按 JLS 8.4.7完成 突然(这里通过返回语句或异常突然指示)。

如果一个方法被声明为具有返回类型,那么编译时 如果该方法的主体能够正常完成,则会发生错误。 换句话说,具有返回类型的方法只能使用 提供值返回的 return 语句; < strong > 不允许这样做 “从它身体的末端掉下来”

编译器根据 JLS 14.21无法到达的声明中定义的规则查看 正常终止妊娠是否可行,因为它也定义了正常完成的规则。

值得注意的是,针对不可达语句的规则只针对具有定义的 true常量表达式的循环:

一个 while 语句可以正常地完成 以下是正确的:

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

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

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

这些规则意味着具有常量 true 表达式且没有 breakwhile语句是 从未被认为是正常完成的,因此它下面的任何代码都是 从来不被认为是可以联系到的。方法的末尾在循环的下面,因为循环下面的所有内容都是不可到达的,所以方法的末尾也是如此,因此方法不可能是 完全正常(编译器寻找的就是 完全正常)。

另一方面,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-语句 可以正常完成或 else 语句可以正常完成。

    • 如果 if-then-else 语句为 条件表达式不是常量表达式 其值为假

    • 当 if-then-else 语句为 条件表达式不是常量表达式 其值为真

这种方法与其他对照的治疗方法是一致的 但是,为了允许使用 if 语句 方便地为“条件编译”的目的,实际的规则 不一样。

例如,下面的语句导致编译时 错误:

因为语句 x=3;不可达; 但表面上类似的情况是:

if (false) { x=3; }不会导致编译时错误 编译器最佳化可能会意识到,abc 0的声明永远不会是 语句中省略该语句的代码 生成的类文件,但语句 x=3;不被视为 “无法到达”的技术含义在这里指明。

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

然后编写如下代码:

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

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

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

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