Fehlende return-Anweisung in einer nicht ungültigen Methode wird kompiliert

189

Ich bin auf eine Situation gestoßen, in der einer nicht ungültigen Methode eine return- Anweisung fehlt und der Code immer noch kompiliert wird. Ich weiß, dass die Anweisungen nach der while-Schleife nicht erreichbar sind (toter Code) und niemals ausgeführt werden würden. Aber warum warnt der Compiler nicht einmal davor, etwas zurückzugeben? Oder warum sollte eine Sprache es uns ermöglichen, eine nicht leere Methode mit einer Endlosschleife zu haben und nichts zurückzugeben?

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

Wenn ich der while-Schleife eine break-Anweisung (auch eine bedingte) hinzufüge, beschwert sich der Compiler über die berüchtigten Fehler: Method does not return a valuein Eclipse und Not all code paths return a valuein Visual Studio.

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

Dies gilt sowohl für Java als auch für C #.

cPu1
quelle
3
Gute Frage. Der Grund dafür würde mich interessieren.
Erik Schierboom
18
Eine Vermutung: Es ist eine Endlosschleife, also ist ein Rücklaufregelungsfluss irrelevant?
Lews Therin
5
"Warum sollte eine Sprache es uns ermöglichen, eine nicht leere Methode mit einer Endlosschleife zu haben und nichts zurückzugeben?" <- Obwohl dies scheinbar dumm ist, könnte die Frage auch umgekehrt werden: Warum sollte dies nicht erlaubt sein? Ist das übrigens der eigentliche Code?
28.
3
Für Java finden Sie in dieser Antwort eine nette Erklärung .
Keppil
4
Wie andere erklärt haben, liegt es daran, dass der Compiler klug genug ist, um zu wissen, dass die Schleife unendlich ist. Beachten Sie jedoch, dass der Compiler nicht nur zulässt, dass die Rückgabe fehlt, sondern sie auch erzwingt , da er alles weiß, nachdem die Schleife nicht erreichbar ist. Zumindest in Netbeans wird es sich buchstäblich darüber beschweren, unreachable statementob es nach der Schleife etwas gibt .
Supr

Antworten:

240

Warum sollte eine Sprache es uns ermöglichen, eine nicht leere Methode mit einer Endlosschleife zu haben und nichts zurückzugeben?

Die Regel für nicht ungültige Methoden lautet, dass jeder zurückgegebene Codepfad einen Wert zurückgeben muss. Diese Regel ist in Ihrem Programm erfüllt: Null von null Codepfaden, die zurückgeben, geben einen Wert zurück. Die Regel lautet nicht "Jede nicht ungültige Methode muss einen Codepfad haben, der zurückgibt".

Auf diese Weise können Sie Stub-Methoden schreiben wie:

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

Das ist eine nicht leere Methode. Es muss eine nicht leere Methode sein, um die Schnittstelle zu erfüllen. Es erscheint jedoch albern, diese Implementierung illegal zu machen, da sie nichts zurückgibt.

Dass Ihre Methode einen nicht erreichbaren Endpunkt hat, weil a goto(denken Sie daran, a while(true)ist nur eine angenehmere Art zu schreiben goto) anstelle von a throw(was eine andere Form von ist goto), ist nicht relevant.

Warum warnt der Compiler nicht einmal davor, etwas zurückzugeben?

Weil der Compiler keine guten Beweise dafür hat, dass der Code falsch ist. Jemand hat geschrieben while(true)und es scheint wahrscheinlich, dass die Person, die das getan hat, wusste, was sie tat.

Wo kann ich mehr über die Erreichbarkeitsanalyse in C # lesen?

Siehe meine Artikel zu diesem Thema hier:

ATBG: de facto und de jure Erreichbarkeit

Sie können auch die C # -Spezifikation lesen.

Eric Lippert
quelle
Warum dieser Code einen Fehler bei der Kompilierung verursacht: public int doNotReturnAnything() { boolean flag = true; while (flag) { //Do something } //no return } Dieser Code hat auch keinen Haltepunkt. Wie der Compiler nun weiß, ist der Code falsch.
Sandeep Poonia
@SandeepPoonia: Vielleicht möchten Sie die anderen Antworten hier lesen ... Der Compiler kann nur bestimmte Bedingungen erkennen.
Daniel Hilgarth
9
@SandeepPoonia: Das boolesche Flag kann zur Laufzeit geändert werden, da es keine Konstante ist und die Schleife daher nicht unbedingt unendlich ist.
Schmuli
9
@SandeepPoonia: In C # die Spezifikation sagt , dass if, while, for, switch, usw., Verzweigungs Konstrukte , die auf betreiben Konstanten als unbedingte Verzweigungen durch den Compiler behandelt werden. Die genaue Definition des konstanten Ausdrucks finden Sie in der Spezifikation. Die Antworten auf Ihre Fragen finden Sie in der Spezifikation. Mein Rat ist, dass Sie es lesen.
Eric Lippert
1
Every code path that returns must return a valueBeste Antwort. Behebt beide Fragen eindeutig. Danke
cPu1
38

Der Java-Compiler ist intelligent genug, um den nicht erreichbaren Code (den Code nach der whileSchleife) zu finden.

und da es nicht erreichbar ist , macht es keinen Sinn , dort eine returnAussage hinzuzufügen (nach dem whileEnde)

Gleiches gilt für bedingte if

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

Da die boolesche Bedingung someBooleannur entweder trueoder auswerten kann false, muss kein return explizites Nachher angegeben werden if-else, da dieser Code nicht erreichbar ist und Java sich nicht darüber beschwert.

Sanbhat
quelle
4
Ich denke nicht, dass dies die Frage wirklich anspricht. Dies beantwortet, warum Sie keine returnAnweisung in nicht erreichbarem Code benötigen , hat aber nichts damit zu tun, warum Sie keine return Anweisung im OP-Code benötigen .
Bobson
Wenn der Java-Compiler intelligent genug ist, um den nicht erreichbaren Code (den Code nach der while-Schleife) zu finden, haben beide folgenden Codes nicht erreichbaren Code, aber die Methode mit if erfordert eine return-Anweisung, die Methode mit while jedoch nicht. 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
@ Sandepepoonia: Weil der Compiler nicht weiß, dass System.exit(1)das Programm dadurch beendet wird. Es können nur bestimmte Arten von nicht erreichbarem Code erkannt werden.
Daniel Hilgarth
@ Daniel Hilgarth: Ok, der Compiler weiß nicht, System.exit(1)dass er das Programm beenden wird. Wir können jedes verwenden return statement, jetzt erkennt der Compiler das return statements. Und das Verhalten ist das gleiche, Rückgabe erfordert für, if conditionaber nicht für while.
Sandeep Poonia
1
Gute Demonstration des nicht erreichbaren Abschnitts ohne Verwendung eines Sonderfalls wiewhile(true)
Matthäus
17

Der Compiler weiß, dass die whileAusführung der Schleife niemals beendet wird, daher wird die Methode niemals beendet, daher ist eine returnAnweisung nicht erforderlich.

Daniel Hilgarth
quelle
13

Vorausgesetzt, Ihre Schleife wird auf einer Konstanten ausgeführt - der Compiler weiß, dass es sich um eine Endlosschleife handelt -, bedeutet dies, dass die Methode sowieso niemals zurückkehren könnte.

Wenn Sie eine Variable verwenden, erzwingt der Compiler die Regel:

Dies wird nicht kompiliert:

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

    while(x == true) {
        //do something
    }
    //no return statement - won't compile
}
Dave Bish
quelle
Aber was ist, wenn "etwas tun" nicht bedeutet, x in irgendeiner Weise zu ändern? Der Compiler ist nicht so schlau, es herauszufinden? Bum :(
Lews Therin
Nein - sieht nicht so aus.
Dave Bish
@Lews Wenn Sie eine Methode haben, die als return int markiert ist, aber tatsächlich nicht zurückgibt, sollten Sie froh sein, dass der Compiler dies kennzeichnet, sodass Sie die Methode als void markieren oder korrigieren können, wenn Sie dies nicht beabsichtigt haben Verhalten.
MikeFHay
@ MikeFHay Ja, das nicht zu bestreiten.
Lews Therin
1
Ich kann mich irren, aber einige Debugger erlauben das Ändern von Variablen. Während x nicht durch Code geändert wird und durch JIT optimiert wird, kann man x in false ändern und die Methode sollte etwas zurückgeben (wenn dies vom C # -Debugger zugelassen wird).
Maciej Piechotka
11

Die Java-Spezifikation definiert ein Konzept namens Unreachable statements. Sie dürfen keine nicht erreichbare Anweisung in Ihrem Code haben (dies ist ein Fehler bei der Kompilierung). Nach der Weile dürfen Sie nicht einmal mehr eine return-Anweisung haben (true). Anweisung in Java. Eine while(true);Anweisung macht die folgenden Anweisungen per Definition nicht erreichbar, daher benötigen Sie keine returnAnweisung.

Beachten Sie, dass das Problem des Anhaltens im allgemeinen Fall zwar nicht zu entscheiden ist, die Definition der nicht erreichbaren Anweisung jedoch strenger ist als nur das Anhalten. Es entscheidet über ganz bestimmte Fälle, in denen ein Programm definitiv nicht anhält. Der Compiler ist theoretisch nicht in der Lage, alle Endlosschleifen und nicht erreichbaren Anweisungen zu erkennen, muss jedoch bestimmte in der Spezifikation definierte Fälle erkennen (z. B. den while(true)Fall).

Konstantin Yovkov
quelle
7

Der Compiler ist klug genug, um herauszufinden, dass Ihre whileSchleife unendlich ist.

Der Compiler kann also nicht für Sie denken. Es kann nicht erraten, warum Sie diesen Code geschrieben haben. Gleiches gilt für die Rückgabewerte von Methoden. Java wird sich nicht beschweren, wenn Sie mit den Rückgabewerten der Methode nichts anfangen.

Um Ihre Frage zu beantworten:

Der Compiler analysiert Ihren Code und nachdem er herausgefunden hat, dass kein Ausführungspfad dazu führt, dass das Ende der Funktion abfällt, endet er mit OK.

Es kann legitime Gründe für eine Endlosschleife geben. Beispielsweise verwenden viele Apps eine Endlosschleife. Ein weiteres Beispiel ist ein Webserver, der auf unbestimmte Zeit auf Anforderungen warten kann.

Adam Arold
quelle
7

In der Typentheorie gibt es so etwas wie den unteren Typ, der eine Unterklasse jedes anderen Typs (!) Ist und unter anderem verwendet wird, um die Nichtbeendigung anzuzeigen. (Ausnahmen können als eine Art Nichtbeendigung gelten - Sie beenden nicht über den normalen Pfad.)

Aus theoretischer Sicht kann davon ausgegangen werden, dass diese nicht terminierenden Anweisungen etwas vom Bottom-Typ zurückgeben, das ein Subtyp von int ist. Sie erhalten also (irgendwie) Ihren Rückgabewert schließlich aus einer Typperspektive. Und es ist vollkommen in Ordnung, dass es keinen Sinn macht, dass ein Typ eine Unterklasse von allem anderen sein kann, einschließlich int, weil Sie nie einen zurückgeben.

In jedem Fall erkennen Compiler (Compiler-Writer) über die explizite Typentheorie oder nicht, dass es dumm ist, nach einer nicht abschließenden Anweisung nach einem Rückgabewert zu fragen: Es gibt keinen möglichen Fall, in dem Sie diesen Wert benötigen könnten. (Es kann schön sein, wenn Ihr Compiler Sie warnt, wenn er weiß, dass etwas nicht beendet wird, aber es sieht so aus, als ob Sie möchten, dass es etwas zurückgibt. Aber das ist besser für Stilprüfer a la lint, da Sie möglicherweise die Typensignatur benötigen, die Weg aus einem anderen Grund (z. B. Unterklassen), aber Sie wollen wirklich keine Kündigung.)

Rex Kerr
quelle
6

Es gibt keine Situation, in der die Funktion ihr Ende erreichen kann, ohne einen geeigneten Wert zurückzugeben. Daher gibt es für den Compiler nichts zu beanstanden.

Graham Borland
quelle
5

Visual Studio verfügt über die Smart Engine, die erkennt, ob Sie einen Rückgabetyp eingegeben haben. In der Funktion / Methode sollte eine return-Anweisung enthalten sein.

Wie in PHP Ihr Rückgabetyp ist wahr, wenn Sie nichts zurückgegeben haben. Der Compiler erhält 1, wenn nichts zurückgegeben wurde.

Ab diesem Zeitpunkt

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

Der Compiler weiß, dass die Anweisung selbst unendlich ist, um sie nicht zu berücksichtigen. und der PHP-Compiler wird automatisch wahr, wenn Sie eine Bedingung in Ausdruck von while schreiben.

Bei VS wird jedoch kein Fehler im Stapel zurückgegeben.

Tarun Gupta
quelle
4

Ihre while-Schleife wird für immer laufen und daher währenddessen nicht nach draußen kommen. es wird weiterhin ausgeführt. Daher ist der äußere Teil von while {} nicht erreichbar und es macht keinen Sinn, eine Rückgabe zu schreiben oder nicht. Der Compiler ist intelligent genug, um herauszufinden, welcher Teil erreichbar ist und welcher nicht.

Beispiel:

public int xyz(){
    boolean x=true;

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

    // no return statement
}

Der obige Code wird nicht kompiliert, da der Wert der Variablen x im Hauptteil der while-Schleife geändert werden kann. Dies macht den äußeren Teil der while-Schleife erreichbar! Und daher wird der Compiler den Fehler "Keine Rückgabeanweisung gefunden" auslösen.

Der Compiler ist nicht intelligent genug (oder eher faul;)), um herauszufinden, ob der Wert von x geändert wird oder nicht. Hoffe das klärt alles.

kunal18
quelle
7
In diesem Fall geht es eigentlich nicht darum, dass der Compiler klug genug ist. In C # 2.0 war der Compiler klug genug, um zu wissen, dass int x = 1; while(x * 0 == 0) { ... }es sich um eine Endlosschleife handelt. Die Spezifikation besagt jedoch, dass der Compiler nur dann Steuerflussabzüge vornehmen sollte, wenn der Schleifenausdruck konstant ist , und die Spezifikation definiert einen konstanten Ausdruck so , dass er keine Variablen enthält . Der Compiler war daher zu schlau . In C # 3 habe ich den Compiler an die Spezifikation angepasst, und seitdem sind diese Ausdrücke absichtlich weniger klug.
Eric Lippert
4

"Warum warnt der Compiler nicht einmal davor, etwas zurückzugeben? Oder warum erlaubt uns eine Sprache, eine nicht leere Methode mit einer Endlosschleife zu haben und nichts zurückzugeben?"

Dieser Code ist auch in allen anderen Sprachen gültig (wahrscheinlich außer Haskell!). Weil die erste Annahme ist, dass wir "absichtlich" Code schreiben.

Und es gibt Situationen, in denen dieser Code vollständig gültig sein kann, beispielsweise wenn Sie ihn als Thread verwenden. oder wenn a zurückgegeben wird Task<int>, können Sie eine Fehlerprüfung basierend auf dem zurückgegebenen int-Wert durchführen, der nicht zurückgegeben werden sollte.

Kaveh Shahbazian
quelle
3

Ich kann mich irren, aber einige Debugger erlauben das Ändern von Variablen. Während x nicht durch Code geändert wird und durch JIT optimiert wird, kann man x in false ändern und die Methode sollte etwas zurückgeben (wenn dies vom C # -Debugger zugelassen wird).


quelle
1

Die Besonderheiten des Java-Falls (die wahrscheinlich dem C # -Fall sehr ähnlich sind) hängen damit zusammen, wie der Java-Compiler bestimmt, ob eine Methode zurückkehren kann.

Insbesondere gilt die Regel, dass eine Methode mit einem Rückgabetyp nicht normal abgeschlossen werden darf und stattdessen immer abgeschlossen werden muss abrupt (hier abrupt über eine return-Anweisung oder eine Ausnahme) gemäß JLS 8.4.7 abgeschlossen werden muss .

Wenn für eine Methode ein Rückgabetyp deklariert wird, tritt ein Fehler zur Kompilierungszeit auf, wenn der Hauptteil der Methode normal abgeschlossen werden kann. Mit anderen Worten, eine Methode mit einem Rückgabetyp darf nur mithilfe einer return-Anweisung zurückgegeben werden, die eine Wertrückgabe bereitstellt. es ist nicht erlaubt, "das Ende seines Körpers fallen zu lassen" .

Der Compiler prüft, ob eine normale Beendigung vorliegt anhand der in JLS 14.21 Unreachable Statements definierten Regeln, möglich ist, da er auch die Regeln für die normale Fertigstellung definiert.

Insbesondere die Regeln für nicht erreichbare Anweisungen machen einen Sonderfall nur für Schleifen, die eine definierte haben true konstanten Ausdruck haben:

Eine while-Anweisung kann normal ausgeführt werden, wenn mindestens eine der folgenden Aussagen zutrifft:

  • Die while-Anweisung ist erreichbar und der Bedingungsausdruck ist kein konstanter Ausdruck (§15.28) mit dem Wert true.

  • Es gibt eine erreichbare break-Anweisung, die die while-Anweisung beendet.

Wenn die whileAnweisung normal ausgeführt werden kann , ist eine folgende return-Anweisung erforderlich, da der Code als erreichbar angesehen wird und jede whileSchleife ohne erreichbare break-Anweisung oder Konstantetrue Ausdruck als normal ausgeführt werden kann.

Diese Regeln bedeuten , dass Ihre whileAussage mit einem konstanten wahren Ausdruck und ohne breakwird normalerweise nie abgeschlossen betrachtet , und so jeder Code unten ist nie erreichbar sein betrachtet . Das Ende der Methode befindet sich unterhalb der Schleife, und da alles unterhalb der Schleife nicht erreichbar ist, ist dies auch das Ende der Methode, und daher kann die Methode möglicherweise nicht normal abgeschlossen werden (wonach der Complier sucht).

if Anweisungen hingegen haben keine besondere Ausnahme für konstante Ausdrücke, die Schleifen gewährt werden.

Vergleichen Sie:

// I have a compiler error!
public boolean testReturn()
{
    final boolean condition = true;

    if (condition) return true;
}

Mit:

// I compile just fine!
public boolean testReturn()
{
    final boolean condition = true;

    while (condition)
    {
        return true;
    }
}

Der Grund für die Unterscheidung ist sehr interessant und beruht auf dem Wunsch, bedingte Kompilierungsflags zuzulassen, die keine Compilerfehler verursachen (vom JLS):

Man könnte erwarten, dass die if-Anweisung folgendermaßen behandelt wird:

  • Eine Wenn-Dann-Anweisung kann normal ausgeführt werden, wenn mindestens eine der folgenden Aussagen zutrifft:

    • Die if-then-Anweisung ist erreichbar und der Bedingungsausdruck ist kein konstanter Ausdruck, dessen Wert wahr ist.

    • Die then-Anweisung kann normal abgeschlossen werden.

    Die then-Anweisung ist erreichbar, wenn die if-then-Anweisung erreichbar ist und der Bedingungsausdruck kein konstanter Ausdruck ist, dessen Wert falsch ist.

  • Eine if-then-else-Anweisung kann normal ausgeführt werden, wenn die then-Anweisung normal oder die else-Anweisung normal ausgeführt werden kann.

    • Die then-Anweisung ist erreichbar, wenn die if-then-else-Anweisung erreichbar ist und der Bedingungsausdruck kein konstanter Ausdruck ist, dessen Wert falsch ist.

    • Die else-Anweisung ist erreichbar, wenn die if-then-else-Anweisung erreichbar ist und der Bedingungsausdruck kein konstanter Ausdruck ist, dessen Wert wahr ist.

Dieser Ansatz wäre mit der Behandlung anderer Kontrollstrukturen vereinbar. Damit die if-Anweisung jedoch bequem für Zwecke der "bedingten Kompilierung" verwendet werden kann, unterscheiden sich die tatsächlichen Regeln.

Die folgende Anweisung führt beispielsweise zu einem Fehler bei der Kompilierung:

while (false) { x=3; }weil die Aussage x=3;nicht erreichbar ist; aber der oberflächlich ähnliche Fall:

if (false) { x=3; }führt nicht zu einem Fehler bei der Kompilierung. Ein optimierender Compiler kann erkennen, dass die Anweisung x=3;niemals ausgeführt wird, und kann den Code für diese Anweisung aus der generierten Klassendatei weglassen, aber die Anweisung x=3;wird im hier angegebenen technischen Sinne nicht als "nicht erreichbar" angesehen.

Der Grund für diese unterschiedliche Behandlung besteht darin, Programmierern die Definition von "Flag-Variablen" zu ermöglichen, wie z.

static final boolean DEBUG = false; und schreiben Sie dann Code wie:

if (DEBUG) { x=3; } Die Idee ist, dass es möglich sein sollte, den Wert von DEBUG von false in true oder von true in false zu ändern und dann den Code ohne weitere Änderungen am Programmtext korrekt zu kompilieren.

Warum führt die bedingte break-Anweisung zu einem Compilerfehler?

Wie in den Regeln für die Erreichbarkeit von Schleifen angegeben, kann eine while-Schleife auch normal abgeschlossen werden, wenn sie eine erreichbare break-Anweisung enthält. Da die Regeln für die Erreichbarkeit der then- Klausel einer ifAnweisung die Bedingung der Anweisung überhaupt nicht berücksichtigen, gilt eine solche bedingte Anweisung dannifif Klausel immer als erreichbar angesehen.

Ist das breakerreichbar, so gilt der Code nach der Schleife wieder als erreichbar. Da es keinen erreichbaren Code gibt, der zu einer abrupten Beendigung nach der Schleife führt, wird die Methode als normal abgeschlossen angesehen, und der Compiler kennzeichnet sie als Fehler.

Trevor Freeman
quelle