Відсутній оператор повернення у недійсному методі компілюється


189

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

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

Якщо я додаю заяву про перерву (навіть умовну) у циклі 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
Приємне запитання. Мене зацікавить причина цього.
Erik Schierboom

18
Здогадка: це нескінченна петля, тому потік управління поверненням не має значення?
Lews Therin

5
"Чому мова дозволить нам мати недійсний метод, що має нескінченний цикл і нічого не повертає?" <- хоча, здавалося б, нерозумно, питання також може бути відмінено: чому б це не було дозволено? Це, до речі, справжній код?
fge

3
Для Java ви можете знайти приємне пояснення у цій відповіді .
Keppil

4
Як пояснили інші, це тому, що компілятор досить розумний, щоб знати, що цикл нескінченний. Зауважте, що компілятор не тільки дозволяє повернути відсутність, але він застосовує його, оскільки він знає що-небудь після циклу недоступним. Принаймні в Netbeans, він буквально поскаржиться, unreachable statementчи є щось після циклу.
Супр

Відповіді:


240

Чому мова дозволить нам мати недійсний метод, що має нескінченний цикл і нічого не повертає?

Правило для недійсних методів є кожен шлях коду, який повертає, повинен повернути значення , і це правило задоволено у вашій програмі: нуль із нульових кодових шляхів, які повертають, повертають значення. Правило не є "кожен недійсний метод повинен мати шлях коду, який повертається".

Це дає змогу писати заглушкові методи, такі як:

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

Це недійсний метод. Це має бути непусто метод для того , щоб задовольнити інтерфейс. Але здається нерозумним зробити цю реалізацію незаконною, оскільки вона нічого не повертає.

Що ваш метод має недосяжну кінцеву точку через 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

@SandeepPoonia: Ви можете прочитати тут інші відповіді ... Компілятор може виявити лише певні умови.
Даніель Хілгарт

9
@SandeepPoonia: Буловий прапор можна змінити під час виконання, оскільки це не const, тому цикл не обов'язково нескінченний.
Шмулі

9
@SandeepPoonia: У C # специфікація каже , що if, while, for, switchі т.д., гіллясті конструкції , які діють на константах розглядаються як безумовні гілки компілятора. Точне визначення постійного вираження є у специфікації. Відповіді на ваші запитання - у специфікації; моя порада полягає в тому, щоб ти її прочитав.
Ерік Ліпперт

1
Every code path that returns must return a valueНайкраща відповідь. Чітко вирішує обидва питання. Дякую
cPu1

38

Компілятор Java досить розумний, щоб знайти недосяжний код (код після whileциклу)

і оскільки його недосяжно , додавати заяву туди (після закінчення) немає сенсуreturnwhile

те саме стосується і умовного 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 твердження в коді ОП.
Бобсон

Якщо компілятор Java досить розумний, щоб знайти недоступний код (код після циклу 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

@SandeepPoonia: Тому що компілятор не знає, що System.exit(1)вб'є програму. Він може виявляти лише певні типи недосяжного коду.
Даніель Хілгарт

@Daniel Hilgarth: Гаразд компілятор не знає System.exit(1), що вб'є програму, ми можемо використовувати будь-яку return statement, тепер компілятор розпізнає return statements. І поведінка така ж, повернення вимагає, if conditionале не для while.
Sandeep Poonia

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

@MikeFHay Так, не заперечуючи це.
Lews Therin

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

11

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

Зауважте, що хоча проблема зупинки не визначається в загальному випадку, визначення Недоступної заяви є більш суворим, ніж просто зупинення. Це вирішують дуже конкретні випадки, коли програма точно не зупиняється. Компілятор теоретично не в змозі виявити всі нескінченні цикли та недоступні оператори, але він повинен виявити конкретні випадки, визначені в специфікації (наприклад, while(true)випадок)


7

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

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

Отже, щоб відповісти на ваше запитання:

Компілятор аналізує ваш код і після з'ясування того, що жоден шлях виконання не призводить до падіння кінця функції, він закінчується ОК.

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


7

У теорії типів існує щось, що називається нижній тип, який є підкласом кожного другого типу (!) І використовується для позначення неприпинення серед іншого. (Винятки можуть вважатися типом припинення - ви не припиняєте звичайний шлях.)

Отже, з теоретичної точки зору, ці твердження, які не закінчуються, можна вважати поверненнями чогось типу Bottom, що є підтипом int, тож ви (вид) отримуєте своє повернене значення з точки зору типу. І це цілком нормально, що не має сенсу, що один тип може бути підкласом всього іншого, включаючи int, тому що ви ніколи не повертаєте його.

У будь-якому випадку, за допомогою явної теорії типів чи ні, компілятори (автори компіляторів) визнають, що запитувати повернути значення після непереривного твердження є нерозумним: не існує жодного можливого випадку, у якому вам може знадобитися це значення. (Це може бути приємно, щоб ваш компілятор попередив вас, коли він знає, що щось не закінчиться, але схоже, що ви хочете щось повернути. Але це краще залишити для перевіряльників стилів a la lint, оскільки, можливо, вам потрібен підпис типу, який спосіб з якихось інших причин (наприклад, підкласифікація), але ви дійсно хочете відміни.)


6

Немає ситуації, в якій функція може досягти свого кінця без повернення відповідного значення. Тому компілятору немає на що скаржитися.


5

Visual studio має розумний двигун для виявлення, якщо ви ввели тип повернення, тоді він повинен мати оператор повернення з функцією / методом.

Як і в PHP Ваш тип повернення справжній, якщо ви нічого не повернули. компілятор отримати 1, якщо нічого не повернулося.

Станом на це

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

Компілятор знає, що в той час як заява сама по собі має нескінченний характер, тому її не враховувати. і компілятор php автоматично стане істинним, якщо ви пишете умову в виразі while.

Але не у випадку з VS це поверне вам помилку в стеці.


4

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

Приклад:

public int xyz(){
    boolean x=true;

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

    // no return statement
}

Наведений вище код не буде компілюватися, оскільки може статися випадок, що значення змінної x змінюється всередині тіла циклу 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 може нормально завершити iff принаймні одне з наступного:

  • Оператор while доступний, а вираз умови - це не постійний вираз (§15.28) зі значенням true.

  • Існує заява про перерву, яка виходить з оператора 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 може нормально завершити iff принаймні одне з наступних дій:

    • Оператор if-then доступний, а вираз умовою не є постійним виразом, значення якого є істинним.

    • Тоді твердження може нормально завершитися.

    Тоді-оператор є доступним, якщо вислів if-then є досяжним, а вираз умовою не є постійним виразом, значення якого помилкове.

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

    • Тоді-оператор є доступним, якщо вислів 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, а потім правильно скласти код без інших змін у тексті програми.

Чому констатація умовного розриву призводить до помилки компілятора?

Як зазначено в правилах доступності циклу, цикл у той час може також нормально завершитися, якщо він містить заяву про доступність перерви. Оскільки правила досяжності НЕ давало нічого ifпро в тому п не приймає умову з ifдо уваги на всіх, такий умовне ifзаяву , в тому становище, завжди вважаються досяжним.

Якщо breakдосяжна, код після циклу ще раз вважається досяжним. Оскільки немає доступного коду, який призводить до різкого припинення після циклу, метод вважається таким, що може завершитись нормально, і тому компілятор відзначає це як помилку.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.