Отсутствует оператор return в компиляторе не-void

Я столкнулся с ситуацией, когда не-void метод отсутствует оператор return, и код все еще компилируется. Я знаю, что утверждения после цикла while недоступны (мертвый код) и никогда не будут выполнены. Но почему компилятор даже не предупреждает о возвращении чего-то? Или почему язык позволяет нам иметь непустой метод, имеющий бесконечный цикл и ничего не возвращающий?

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

Если в цикле while добавить оператор break (даже условный), компилятор жалуется на печально известные ошибки: "Метод не возвращает значение" (Eclipse) и "Не все пути кода возвращают значение" ( Visual Studio)

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

Это справедливо как для Java, так и для С#

Ответ 1

Почему язык позволяет нам иметь непустой метод, имеющий бесконечный цикл и ничего не возвращающий?

Правило для непустых методов - это каждый возвращаемый путь кода, который должен возвращать значение, и это правило выполняется в вашей программе: нуль из нулевых путей кода, возвращаемых, возвращает значение. Правило не "каждый не-void метод должен иметь путь к коду, который возвращает".

Это позволяет вам писать такие заглушки, как:

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

Это непустой метод. Он должен быть непустым методом, чтобы удовлетворить интерфейс. Но кажется глупым сделать эту реализацию незаконной, потому что она ничего не возвращает.

То, что ваш метод имеет недостижимую конечную точку из-за goto (помните, a while(true) является просто более приятным способом записи goto) вместо throw (что является другой формой goto) не имеет значения.

Почему компилятор даже не предупреждает о возвращении чего-либо?

Поскольку у компилятора нет веских доказательств того, что код неверен. Кто-то написал while(true), и кажется вероятным, что тот, кто это сделал, знал, что они делают.

Где я могу узнать больше о анализе достижимости в С#?

Смотрите мои статьи по этому вопросу, здесь:

ATBG: фактическая и де-юре достижимость

И вы также можете рассмотреть возможность чтения спецификации С#.

Ответ 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, нет необходимости предоставлять return явно после if-else, потому что этот код недоступен, и Java не жалуется на это.

Ответ 3

Компилятор знает, что цикл while никогда не перестанет выполняться, поэтому метод никогда не завершится, поэтому оператор return не нужен.

Ответ 4

Учитывая, что ваш цикл выполняется на константе - компилятор знает, что это бесконечный цикл - это означает, что метод никогда не сможет вернуться, в любом случае.

Если вы используете переменную - компилятор выполнит правило:

Это не скомпилируется:

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

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

Ответ 5

Спецификация Java определяет понятие под названием Unreachable statements. Вам не разрешено иметь недопустимый оператор в вашем коде (это ошибка времени компиляции). Вам даже не разрешено иметь оператор возврата после этого (правда); в Java. Оператор while(true); делает следующие инструкции недоступными по определению, поэтому вам не нужен оператор return.

Обратите внимание, что хотя проблема с остановкой неразрешима в общем случае, определение Unreachable Statement более строгое, чем просто остановка. Он решает очень конкретные случаи, когда программа определенно не останавливается. Компилятор теоретически не способен обнаруживать все бесконечные циклы и недостижимые операторы, но он должен обнаруживать конкретные случаи, определенные в спецификации (например, случай while(true))

Ответ 6

Компилятор достаточно умен, чтобы узнать, что ваш цикл while бесконечен.

Поэтому компилятор не может думать о вас. Он не может угадать , почему написал этот код. То же самое означает возвращаемые значения методов. Java не будет жаловаться, если вы ничего не делаете с возвращаемыми значениями методов.

Итак, чтобы ответить на ваш вопрос:

Компилятор анализирует ваш код и выясняет, что ни один из путей выполнения не приводит к падению конца функции, заканчивающейся с помощью OK.

Для бесконечного цикла могут быть законные причины. Например, многие приложения используют бесконечный основной цикл. Другим примером является веб-сервер, который может бесконечно ждать запросов.

Ответ 7

Нет ситуации, когда функция может достигать своего конца, не возвращая соответствующее значение. Поэтому компилятор не жалуется.

Ответ 8

В теории типов есть что-то, называемое нижним типом, который является подклассом любого другого типа (!) и используется для указания отсутствия прерывания между прочим. (Исключения могут считаться типом отсутствия прерывания - вы не завершаетесь по нормальному пути.)

Итак, с теоретической точки зрения, эти утверждения, которые не являются завершающими, можно считать возвратом чего-то типа Bottom, который является подтипом int, поэтому вы (вроде) получаете возвращаемое значение в конце концов с точки зрения типа, И это совершенно нормально, что не имеет никакого смысла, что один тип может быть подклассом всего остального, включая int, потому что вы никогда не возвращаете его.

В любом случае, используя явную теорию типов или нет, компиляторы (составители компилятора) признают, что запрос возвращаемого значения после утверждения без конца является глупым: нет возможного случая, когда это может понадобиться. (Может быть, приятно, что ваш компилятор предупреждает вас, когда он знает, что что-то не закончится, но похоже, что вы хотите, чтобы он что-то возвращал. Но это лучше для стильных мастеров a la lint, поскольку, возможно, вам нужна подпись типа, путь по какой-то другой причине (например, подклассификация), но вы действительно хотите не прекращать работу.)

Ответ 9

В Visual Studio есть интеллектуальный движок, чтобы определить, набрал ли вы тип возвращаемого значения, тогда он должен иметь оператор return в функции/методе.

Как и в PHP, ваш тип возврата является истинным, если вы ничего не вернули. компилятор получает 1, если ничего не вернулось.

С этой точки зрения

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

Компилятор знает, что в то время как сам оператор имеет бесконечную природу, поэтому не следует его рассматривать. и php-компилятор автоматически получит значение true, если вы напишете условие в выражении while.

Но не в случае VS он вернет вам ошибку в стеке.

Ответ 10

Ваш цикл while будет работать вечно и, следовательно, не выйдет наружу; он будет продолжать выполняться. Следовательно, внешняя часть while {} недостижима и нет точки в письме return или нет. Компилятор достаточно умен, чтобы понять, какая часть достижима, а какая нет.

Пример:

public int xyz(){
    boolean x=true;

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

    // no return statement
}

Вышеприведенный код не будет компилироваться, потому что может быть случай, когда значение переменной x изменяется внутри тела цикла while. Таким образом, это делает внешнюю часть цикла while доступной! И, следовательно, компилятор выдаст ошибку "return return found".

Компилятор недостаточно интеллектуальный (или довольно ленивый;)), чтобы выяснить, будет ли изменено значение x или нет. Надеюсь, это очистит все.

Ответ 11

"Почему компилятор даже не предупреждает о возвращении чего-либо? Или почему язык позволяет нам иметь непустой метод, имеющий бесконечный цикл и ничего не возвращающий?".

Этот код действует и на всех других языках (возможно, кроме Haskell!). Поскольку первое предположение заключается в том, что мы "намеренно" пишем некоторый код.

И есть ситуации, когда этот код может быть полностью действительным, как если бы вы использовали его как поток; или если он возвращает Task<int>, вы можете выполнить некоторую проверку ошибок на основе возвращаемого значения int, которое не должно быть возвращено.

Ответ 12

Я могу ошибаться, но некоторые отладчики допускают модификацию переменных. Здесь, когда x не изменен кодом, и он будет оптимизирован JIT, вы можете изменить x на false и метод должен что-то вернуть (если такая вещь разрешена отладчиком С#).

Ответ 13

Специфика Java-примера для этого (вероятно, очень похожего на случай С#) заключается в том, как компилятор Java определяет, может ли метод вернуться.

В частности, правила заключаются в том, что метод с возвращаемым типом не может нормально функционировать и должен всегда выполняться внезапно (внезапно здесь указывая через оператор return или исключение) на JLS 8.4.7.

Если метод объявлен как возвращаемый тип, тогда время компиляции ошибка возникает, если тело метода может завершиться нормально. Другими словами, метод с возвращаемым типом должен возвращаться только с помощью оператор возврата, который обеспечивает возврат значения; не разрешено "опустите конец своего тела" .

Компилятор смотрит, возможно ли обычное завершение, основанное на правилах, определенных в JLS 14.21 Unreachable Statementments, поскольку он также определяет правила для нормального завершение.

Примечательно, что правила для недостижимых операторов делают особый случай только для циклов, которые имеют определенное выражение константы true:

Оператор while может завершиться нормально, если хотя бы один из верно следующее:

  • Оператор while доступен и выражение условия не является постоянное выражение (§15.28) со значением true.

  • Существует допустимый оператор break, который завершает оператор while.

Итак, если оператор while может завершиться нормально, то под ним требуется оператор return, так как код считается достижимым, и любой цикл while без оператора достижимого разрыва или постоянного выражения true считается способным нормально.

Эти правила означают, что ваш оператор while с константным истинным выражением и без 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 может завершиться нормально.

    Оператор then доступен, если утверждение if-then доступно и выражение условия не является постоянным выражением, значение которого false.

  • Оператор if-then-else может завершиться нормально, если then-statement может завершиться нормально, иначе инструкция else может завершиться нормально.

    • Оператор then доступен, если оператор if-then-else равен достижимое, а выражение условия не является постоянным выражением значение которого ложно.

    • Оператор else доступен, если оператор if-then-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, а затем скомпилируйте код правильно, без каких-либо изменений в тексте программы.

Почему оператор условного разрыва приводит к ошибке компилятора?

Как указано в правилах достижимости цикла, цикл while также может нормально выполняться, если он содержит оператор достижимого разрыва. Поскольку правила для достижимости оператора if, то предложение не принимает во внимание условие if вообще, такой условный оператор if, тогда предложение всегда считается достижимым.

Если break достижим, тогда код после цикла снова считается доступным. Поскольку нет кода достижимости, который приводит к резкому завершению после цикла, тогда метод считается выполненным нормально, поэтому компилятор помещает его как ошибку.