void以外のメソッドのコンパイルでreturnステートメントが見つからない


189

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#の両方に当てはまります。


3
いい質問です この理由に興味があります。
Erik Schierboom 2013年

18
推測:それは無限ループなので、戻り制御フローは無関係ですか?
Lews Therin 2013年

5
「言語によって、無限ループを持ち、何も返さないvoid以外のメソッドを使用できるのはなぜですか?」<-馬鹿げているように見えますが、質問を逆にすることもできます。なぜこれが許されないのですか?ところで、これは実際のコードですか?
2013年

3
Javaの場合、この回答ですばらしい説明を見つけることができます。
ケッピル2013年

4
他の人が説明したように、それはコンパイラがループが無限であることを知るのに十分賢いからです。ただし、コンパイラーは戻り値が欠落することを許可するだけでなく、ループに到達できないことを認識ているため、それを強制します。少なくともNetbeansでは、ループの後に何かunreachable statementがあるどうかについて文字通り文句を言うでしょう。
2013年

回答:


240

言語が無限ループを持ち、何も返さないvoid以外のメソッドを持つことができるのはなぜですか?

非voidメソッドのルールは、返されるすべてのコードパスが値を返す必要があることであり、そのルールはプログラムで満たされます。ゼロを返すコードパスのうちゼロは値を返します。ルールは「すべてのvoid以外のメソッドは返すコードパスを持つ必要がある」ではありません。

これにより、次のようなスタブメソッドを記述できます。

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

それは非voidメソッドです。インターフェースを満たすためには、それ voidでないメソッドでなければなりません。しかし、何も返さないので、この実装を違法にするのはばかげているようです。

あなたの方法が原因の到達不能のエンドポイントを持っていることをgoto(覚えて、while(true)書き込みにだけ、より快適な方法であるgoto代わりに)throw(の別の形態であるgoto)は関係ありません。

コンパイラが何かを返すことについて警告さえしないのはなぜですか?

なぜなら、コンパイラにはコードが間違っているという証拠はありません。誰かが書いてwhile(true)、それをした人は彼らが何をしていたか知っていたようです。

C#での到達可能性分析の詳細はどこで確認できますか?

この件に関する私の記事をご覧ください:

ATBG:デファクトおよびデジュールの到達可能性

また、C#仕様を読むことも検討してください。


このコードがコンパイル時エラーを出す理由:public int doNotReturnAnything() { boolean flag = true; while (flag) { //Do something } //no return } このコードにもブレークポイントはありません。したがって、コンパイラがコードを認識する方法が間違っています。
Sandeep Poonia 2013年

@SandeepPoonia:他の回答をここで読むことをお勧めします...コンパイラは特定の条件のみを検出できます。
Daniel Hilgarth、2013年

9
@SandeepPoonia:ブールフラグはconstではないため、実行時に変更でき、ループは必ずしも無限ではありません。
Schmuli 2013年

9
@SandeepPoonia:ではC#の仕様はそれが言うifwhileforswitch、など、で動作する分岐構造定数は、コンパイラによって無条件分岐として扱われます。定数式の正確な定義は仕様にあります。あなたの質問に対する答えは仕様書にあります。私のアドバイスは、あなたがそれを読むことです。
Eric Lippert、2013年

1
Every code path that returns must return a valueベストアンサー。両方の質問に明確に対処します。ありがとう
cPu1 2013年

38

Javaコンパイラーは到達できないコード(whileループ後のコード)を見つけるのに十分スマートです

そして到達できないので、そこにreturnステートメントを追加しても意味がありませんwhile

条件付きでも同じ if

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

ブール条件someBooleantrueorまたはのいずれかにしか評価できないため、そのコードに到達できず、Javaがそれについて文句を言わないためfalsereturn 明示的に afterを指定する必要はありません。if-else


4
これで問題が解決したとは思いません。この答えは、なぜあなたは必要としないreturn、到達不能コード内の文を、しかし、あなたは必要としない理由とは何の関係もありません任意の return OPのコードで声明を。
ボブソン

Javaコンパイラーが到達不能コード(whileループの後のコード)を見つけるのに十分スマートである場合、以下のコードがコンパイルされる理由はどちらも到達不能コードですが、ifを使用するメソッドにはreturnステートメントが必要ですが、whileを使用するメソッドはありません。 public int doNotReturnAnything() { if(true){ System.exit(1); } return 11; } public int doNotReturnAnything() { while(true){ System.exit(1); } return 11;// Compiler error: unreachable code }
Sandeep Poonia 2013年

@SandeepPoonia:コンパイラがプログラムを強制終了するSystem.exit(1)ことを知らないため。特定の種類の到達不能コードのみを検出できます。
Daniel Hilgarth、2013年

@Daniel Hilgarth:OKコンパイラはSystem.exit(1)プログラムを強制終了することを知りません。任意のものを使用できますがreturn statement、コンパイラはを認識しreturn statementsます。動作は同じif conditionですwhile。戻り値にはが必要ですが、は必要ありません。
Sandeep Poonia 2013年

1
次のような特別なケースを使用せずに到達不能セクションの優れたデモンストレーションwhile(true)
マシュー

17

コンパイラーは、whileループが実行を停止しないこと、したがってメソッドが終了しないことを認識しているため、returnステートメントは必要ありません。


13

ループが定数で実行されている場合-コンパイラーはそれが無限ループであることを知っています-とにかくメソッドが戻ることができないことを意味します。

変数を使用する場合、コンパイラーはルールを適用します。

これはコンパイルされません:

// 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 Therin 2013年

いいえ、そのようには見えません。
Dave Bish

@Lews intを返すようにマークされているが実際には返さないメソッドがある場合、コンパイラがこれにフラグを立てて満足するはずです。そのため、メソッドをvoidとしてマークしたり、意図していない場合は修正したりできます。動作。
MikeFHay 2013年

@MikeFHayうん、そう論争していない。
Lews Therin 2013年

1
私は間違っているかもしれませんが、一部のデバッガーは変数の変更を許可します。ここで、xはコードによって変更されず、JITによって最適化されますが、xをfalseに変更してメソッドが何かを返す必要があります(C#デバッガーで許可されている場合)。
Maciej Piechotka 2013年

11

Java仕様では、という概念が定義されていUnreachable statementsます。コードに到達できないステートメントを含めることはできません(コンパイル時エラーです)。while(true)の後にreturnステートメントを置くこともできません。Javaのステートメント。while(true);声明は、定義によって次の文が到達不能になり、したがって、必要のないreturn文を。

一般的なケースでは停止問題は決定できませんが、到達不能ステートメントの定義は単に停止するよりも厳密であることに注意してください。これは、プログラムが確実に停止しない非常に特殊なケースを決定しています。コンパイラーは理論的には、すべての無限ループと到達できないステートメントを検出することはできませんが、仕様で定義されている特定のケース(while(true)ケースなど)を検出する必要があります


7

コンパイラはあなたのことを見つけるのに十分スマートです whileループが無限であるです。

したがって、コンパイラはあなたのために考えることはできません。理由はわからないそのコードを書いたの。メソッドの戻り値も同じです。メソッドの戻り値で何もしなくても、Javaは文句を言わないでしょう。

だから、あなたの質問に答えるには:

コンパイラーはコードを分析し、実行パスが関数の最後から落ちることがないことを確認した後、OKで終了します。

無限ループには正当な理由があるかもしれません。たとえば、多くのアプリは無限メインループを使用しています。別の例は、リクエストを無期限に待機するWebサーバーです。


7

型理論には、ボトム型と呼ばれるものがありますは、他のすべての型(!)のサブクラスであるこれは、とりわけ、非終了を示すために使用されます。(例外は非終了の一種として数えることができます-通常のパスを介して終了することはありません。)

したがって、理論的な観点から見ると、終了しないこれらのステートメントは、intのサブタイプであるBottomタイプの何かを返すと見なすことができるため、結局のところ、タイプの観点から戻り値を取得します。また、実際には1つの型を返さないため、ある型がintを含む他のすべてのサブクラスになる可能性があることはまったく意味がありません。

いずれにせよ、明示的な型理論かどうかにかかわらず、コンパイラー(コンパイラー作成者)は、非終了ステートメントの後に戻り値を要求するのは愚かであることを認識します。その値が必要になる可能性のあるケースはありません。(何かが終了しないことを知っているが、何かを返したいように見える場合にコンパイラーに警告を表示するのは良いことです。しかし、それはスタイルチェッカーのla lintに任せたほうがいいです。何らかの他の理由(サブクラス化など)のための方法ですが、本当に非終了が必要です。)


6

関数が適切な値を返さずに最後まで到達できる状況はありません。したがって、コンパイラーが文句を言うことは何もありません。


5

Visual Studioには、戻り値の型を入力したかどうかを検出するスマートエンジンがあり、関数/メソッドにreturnステートメントが必要です。

PHPの場合と同様、何も返されていない場合、戻り値の型はtrueです。何も返されない場合、コンパイラーは1を取得します。

この時点で

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

コンパイラーは、whileステートメント自体が無限の性質を持っているので、それを考慮しないことを知っています。また、whileの式で条件を記述した場合、phpコンパイラは自動的にtrueになります。

しかし、VSの場合は、スタックにエラーを返します。


4

whileループは永久に実行されるため、その間外に出ることはありません。引き続き実行されます。したがって、while {}の外側の部分に到達できず、returnを書いても意味がありません。コンパイラーは、到達可能な部分と到達できない部分を判別するのに十分なほどインテリジェントです。

例:

public int xyz(){
    boolean x=true;

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

    // no return statement
}

変数xの値がwhileループの本体内で変更される場合があるため、上記のコードはコンパイルされません。これにより、whileループの外側の部分が到達可能になります。したがって、コンパイラーは「returnステートメントが見つかりません」というエラーをスローします。

コンパイラーは、xの値が変更されるかどうかを判断するのに十分なほどインテリジェントではありません(または、怠惰です;))。これですべてがクリアされることを願っています。


7
実際には、この場合、コンパイラが十分に賢いという問題ではありません。C#2.0では、コンパイラーはそれint x = 1; while(x * 0 == 0) { ... }が無限ループであることを認識するのに十分スマートでしたが、仕様では、ループ式が定数である場合にのみコンパイラーが制御フローの推論を行うべきであると述べていますであるであり、変数式を含まないものとして定数式を定義しています。したがって、コンパイラーはあまりにもスマートでした。C#3では、コンパイラーを仕様に一致させました。それ以降、この種の式については意図的にあまりスマートになりません。
Eric Lippert 2013年

4

「コンパイラーが何かを返すことについて警告しないのはなぜですか?または、言語が無限ループを持ち、何も返さないvoid以外のメソッドを持つことができるのはなぜですか?」

このコードは他のすべての言語でも有効です(おそらくHaskellを除いて!)。最初の前提は、「意図的に」コードを記述しているためです。

また、このコードをスレッドとして使用する場合のように、このコードが完全に有効な場合もあります。または、それがを返していたTask<int>場合は、返されたint値に基づいてエラーチェックを行うことができます。


3

私は間違っているかもしれませんが、一部のデバッガーは変数の変更を許可しています。ここで、xはコードによって変更されず、JITによって最適化されますが、xをfalseに変更して、メソッドが何かを返す必要があります(C#デバッガーで許可されている場合)。


1

このJavaケースの詳細(おそらくC#ケースと非常によく似ています)は、Javaコンパイラーがメソッドが戻ることができるかどうかをどのように決定するかに関係しています。

具体的には、JLS 8.4.7に従って、戻り値の型を持つメソッドは正常完了できず、常に突然(ここではreturnステートメントまたは例外を介して示す)完了する必要があるという規則があります。

メソッドが戻り値の型を持つように宣言されている場合、メソッドの本体が正常に完了すると、コンパイル時エラーが発生します。つまり、戻り値の型を持つメソッドは、値を返すreturnステートメントを使用することによってのみ戻る必要があります。「体の端を降ろす」ことはできません

コンパイラーは、JLS 14.21到達不能ステートメントで定義された規則に基づいて、正常終了が可能かどうかを調べます。これは、正常終了の規則も定義しているためです。

特に、到達不能ステートメントのルールは、true定数式が定義されているループに対してのみ特別なケースを作ります:

whileステートメントは、以下の少なくとも1つが当てはまる場合に通常どおり完了できます。

  • whileステートメントに到達でき、条件式が値trueの定数式(15.28)ではありません。

  • whileステートメントを終了する到達可能なbreakステートメントがあります。

したがって、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ステートメントは、以下の少なくとも1つが当てはまる場合に通常どおり完了できます。

    • if-thenステートメントに到達でき、条件式が値がtrueの定数式ではありません。

    • thenステートメントは正常に完了することができます。

    thenステートメントは、if-thenステートメントが到達可能で、条件式が値がfalseの定数式でない場合に到達可能です。

  • if-then-elseステートメントは、thenステートメントが正常に完了できるか、elseステートメントが正常に完了できる場合に限り、正常に完了できます。

    • thenステートメントは、if-then-elseステートメントに到達でき、条件式が値がfalseの定数式でない場合に到達できます。

    • else-statementは、if-then-elseステートメントが到達可能で、条件式が値がtrueの定数式でない場合に到達可能です。

このアプローチは、他の制御構造の扱いと一致します。ただし、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句の到達可能性に関するルールは、の条件をifまったく考慮しないため、このような条件付きifステートメントのthen句は常に到達可能と見なされます。

breakが到達可能である場合、ループ後のコードも到達可能と見なされます。ループ後に突然終了するコードには到達できないため、メソッドは正常に完了したと見なされ、コンパイラーはエラーとしてフラグを立てます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.