Отсутствует инструкция возврата в не пустом методе


189

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

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

Если я добавляю оператор break (даже условный) в цикл while, компилятор жалуется на печально известные ошибки: Method does not return a valueв Eclipse и Not all code paths return a valueв Visual Studio.

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

Это верно как для Java, так и для C #.


3
Хороший вопрос Я был бы заинтересован в причине этого.
Эрик Ширбум

18
Угадайте: это бесконечный цикл, поэтому обратный поток управления не имеет значения?
Льюс Терин

5
"почему язык позволил бы нам иметь не пустой метод, имеющий бесконечный цикл и ничего не возвращающий?" <- хотя это, казалось бы, глупо, вопрос также может быть изменен: почему это не разрешено? Кстати, это актуальный код?
2013 г.

3
Для Java вы можете найти хорошее объяснение в этом ответе .
Кеппил

4
Как объяснили другие, это потому, что компилятор достаточно умен, чтобы знать, что цикл бесконечен. Обратите внимание, что компилятор не только допускает отсутствие возврата, но и применяет его, потому что он знает что-либо после того, как цикл недоступен. По крайней мере, в NetBeans он будет буквально жаловаться на unreachable statementналичие чего-либо после цикла.
Supr

Ответы:


240

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

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

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

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

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

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

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

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

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

Смотрите мои статьи на эту тему здесь:

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

И вы могли бы также рассмотреть чтение спецификации C #.


Так почему этот код дает ошибку времени компиляции: public int doNotReturnAnything() { boolean flag = true; while (flag) { //Do something } //no return } этот код также не имеет точки останова. Итак, теперь, как компилятор знает, что код неверен.
Сандип Пун

@SandeepPoonia: Вы можете прочитать другие ответы здесь ... Компилятор может обнаруживать только определенные условия.
Даниэль Хилгарт

9
@SandeepPoonia: логический флаг можно изменить во время выполнения, поскольку он не является константным, поэтому цикл не обязательно бесконечный.
Шмули

9
@SandeepPoonia: В C # спецификация говорит , что if, while, for, switchи т.д., ветвистые конструкции , которые действуют на константах рассматриваются как безусловные ветви компилятора. Точное определение константного выражения в спецификации. Ответы на ваши вопросы в спецификации; Мой совет, что вы читаете это.
Эрик Липперт

1
Every code path that returns must return a valueЛучший ответ. Четко отвечает на оба вопроса. Спасибо
cPu1

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;
}

поскольку булево условие someBooleanможет вычисляться только для одного trueили false, нет необходимости return явно указывать после if-else, потому что этот код недоступен , и Java не жалуется на это.


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 }
Сандип Пунья

@SandeepPoonia: потому что компилятор не знает, что System.exit(1)убьет программу. Он может обнаруживать только определенные типы недоступного кода.
Даниэль Хилгарт

@Daniel Hilgarth: Хорошо, компилятор не знает, System.exit(1)что убьет программу, мы можем использовать любую return statement, теперь компилятор распознает return statements. И поведение такое же, возврат требуется для, if conditionно не для while.
Сандип Пунья

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, если у вас есть метод, который помечен как возвращающий int, но на самом деле не возвращает, тогда вы должны быть рады, что компилятор помечает это, так что вы можете пометить метод как void или исправить его, если вы не предназначались для этого поведение.
MikeFHay

@MikeFHay Да, не оспаривая это.
Льюс Терин

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

11

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

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


7

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

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

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

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

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


7

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

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

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


6

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


5

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

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

По состоянию на это

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

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

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


4

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

Пример:

public int xyz(){
    boolean x=true;

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

    // no return statement
}

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

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


7
На самом деле, в данном случае вопрос не в том, что компилятор достаточно умен. В C # 2.0 компилятор был достаточно умен, чтобы знать, что int x = 1; while(x * 0 == 0) { ... }это бесконечный цикл, но в спецификации сказано, что компилятор должен делать вычеты потока управления только тогда, когда выражение цикла является постоянным , а спецификация определяет постоянное выражение как не содержащее переменных . Поэтому компилятор был слишком умен . В C # 3 я сделал так, чтобы компилятор соответствовал спецификации, и с тех пор он намеренно стал менее умным в отношении таких выражений.
Эрик Липперт

4

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

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

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


3

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


1

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

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

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

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

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

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

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

  • Существует оператор достижимого прерывания, который выходит из оператора while.

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

    • Оператор then может завершиться нормально.

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

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

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

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

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

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

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.