Napotkałem sytuację, w której w metodzie innej niż void brakuje instrukcji return , a kod nadal się kompiluje. Wiem, że instrukcje po pętli while są nieosiągalne (martwy kod) i nigdy nie zostaną wykonane. Ale dlaczego kompilator nawet nie ostrzega o zwrocie czegoś? Albo dlaczego język miałby pozwolić nam na zastosowanie metody nie-void mającej nieskończoną pętlę i niczego nie zwracającej?
public int doNotReturnAnything() {
while(true) {
//do something
}
//no return statement
}
Jeśli dodam instrukcję break (nawet warunkową) w pętli while, kompilator skarży się na niesławne błędy: Method does not return a value
w Eclipse i Not all code paths return a value
Visual Studio.
public int doNotReturnAnything() {
while(true) {
if(mustReturn) break;
//do something
}
//no return statement
}
Dotyczy to zarówno Java, jak i C #.
unreachable statement
jeśli coś jest po pętli.Odpowiedzi:
Reguła dla metod nie będących nieważnymi jest taka, że każda zwracana ścieżka kodu musi zwracać wartość , a ta reguła jest spełniona w twoim programie: zero z zerowych ścieżek kodu, które zwracają, zwraca wartość. Reguła nie brzmi: „każda metoda nie void musi mieć ścieżkę kodu, która zwraca”.
Umożliwia to pisanie skrótowych metod takich jak:
To metoda nieważna. To musi być non-void metoda w celu zaspokojenia interfejs. Ale niemądre wydaje się uczynienie tej implementacji nielegalną, ponieważ nic nie zwraca.
To, że twoja metoda ma nieosiągalny punkt końcowy z powodu
goto
(pamiętaj, żewhile(true)
to po prostu przyjemniejszy sposób pisaniagoto
) zamiastthrow
(co jest inną formągoto
) nie ma znaczenia.Ponieważ kompilator nie ma dobrych dowodów na to, że kod jest nieprawidłowy. Ktoś napisał
while(true)
i wydaje się prawdopodobne, że osoba, która to zrobiła, wiedziała, co robi.Zobacz moje artykuły na ten temat tutaj:
ATBG: de facto i de jure osiągalność
I możesz również rozważyć przeczytanie specyfikacji C #.
źródło
public int doNotReturnAnything() {
boolean flag = true;
while (flag) {
//Do something
}
//no return
}
Ten kod również nie ma punktu przerwania. Więc teraz, skąd kompilator wie, że kod jest zły.if
,while
,for
,switch
itd, rozgałęziające konstrukty, które działają na stałe traktowane są jako bezwarunkowych oddziałów przez kompilator. Dokładna definicja stałego wyrażenia znajduje się w specyfikacji. Odpowiedzi na twoje pytania znajdują się w specyfikacji; moja rada jest taka, że ją czytasz.Every code path that returns must return a value
Najlepsza odpowiedź. Jasno odnosi się do obu pytań. DziękiKompilator Java jest wystarczająco inteligentny, aby znaleźć nieosiągalny kod (kod po
while
pętli)a ponieważ jest nieosiągalny , nie ma sensu dodawać tam
return
instrukcji (powhile
zakończeniu)to samo dotyczy warunkowego
if
ponieważ warunek logiczny
someBoolean
może tylko ocenić albo,true
albofalse
nie ma potrzeby podawaniareturn
jednoznacznie późniejif-else
, ponieważ ten kod jest nieosiągalny , a Java nie narzeka na to.źródło
return
instrukcji w nieosiągalnym kodzie, ale nie ma to nic wspólnego z tym, dlaczego nie potrzebujesz żadnejreturn
instrukcji w kodzie OP.public int doNotReturnAnything() {
if(true){
System.exit(1);
}
return 11;
}
public int doNotReturnAnything() {
while(true){
System.exit(1);
}
return 11;// Compiler error: unreachable code
}
System.exit(1)
to zabije program. Może wykrywać tylko niektóre typy nieosiągalnego kodu.System.exit(1)
że zabije program, możemy użyć dowolnegoreturn statement
, teraz kompilator rozpoznajereturn statements
. I zachowanie jest takie samo, zwrot wymaga za,if condition
ale nie zawhile
.while(true)
Kompilator wie, że
while
pętla nigdy nie przestanie działać, dlatego metoda nigdy się nie skończy, dlategoreturn
instrukcja nie jest konieczna.źródło
Biorąc pod uwagę, że twoja pętla działa w sposób ciągły - kompilator wie, że jest to pętla nieskończona - co oznacza, że metoda i tak nigdy nie może wrócić.
Jeśli użyjesz zmiennej - kompilator wymusi regułę:
To się nie skompiluje:
źródło
Specyfikacja Java definiuje pojęcie o nazwie
Unreachable statements
. W twoim kodzie nie możesz mieć nieosiągalnej instrukcji (jest to błąd czasu kompilacji). Po pewnym czasie nie wolno ci nawet mieć instrukcji return (prawda); instrukcja w Javie.while(true);
Zestawienie sprawia, że następujące stwierdzenia nieosiągalny z definicji, zatem nie trzeba sięreturn
oświadczenie.Zauważ, że chociaż problem zatrzymania jest nierozstrzygalny w przypadku ogólnym, definicja nieosiągalnego stwierdzenia jest bardziej rygorystyczna niż zwykłe zatrzymanie. Decyduje o bardzo konkretnych przypadkach, w których program na pewno się nie zatrzymuje. Kompilator teoretycznie nie jest w stanie wykryć wszystkich nieskończonych pętli i nieosiągalnych instrukcji, ale musi wykryć określone przypadki zdefiniowane w specyfikacji (na przykład
while(true)
przypadek)źródło
Kompilator jest wystarczająco inteligentny, aby dowiedzieć się, że twoja
while
pętla jest nieskończona.Tak więc kompilator nie może myśleć za Ciebie. Nie może zgadnąć, dlaczego napisałeś ten kod. To samo oznacza zwracane wartości metod. Java nie będzie narzekać, jeśli nie zrobisz nic z wartościami zwracanymi przez metodę.
Tak więc, aby odpowiedzieć na twoje pytanie:
Kompilator analizuje kod i po stwierdzeniu, że żadna ścieżka wykonania nie prowadzi do odpadnięcia końca funkcji, kończy się na OK.
Mogą istnieć uzasadnione powody nieskończonej pętli. Na przykład wiele aplikacji korzysta z nieskończonej pętli głównej. Innym przykładem jest serwer WWW, który może bez końca czekać na żądania.
źródło
W teorii typów istnieje coś, co nazywa się typem dolnym który jest podklasą każdego innego typu (!) I służy między innymi do wskazania braku zakończenia. (Wyjątki mogą być liczone jako rodzaj nieterminacji - nie kończysz normalną ścieżką).
Tak więc z teoretycznego punktu widzenia, te nie kończące się instrukcje można uznać za zwracające coś typu Bottom, który jest podtypem int, więc otrzymujesz (w pewnym sensie) wartość zwrotną mimo wszystko z perspektywy typu. I jest całkowicie w porządku, że nie ma żadnego sensu, że jeden typ może być podklasą wszystkiego innego, w tym int, ponieważ tak naprawdę nigdy go nie zwraca.
W każdym razie, poprzez teorię typu jawnego lub nie, kompilatory (autorzy kompilatorów) rozpoznają, że proszenie o wartość zwracaną po instrukcji nie kończącej jest głupie: nie ma możliwego przypadku, w którym mogłabyś potrzebować tej wartości. (Może być fajnie, gdy twój kompilator ostrzega cię, gdy wie, że coś się nie skończy, ale wygląda na to, że chcesz coś zwrócić. Ale lepiej to zostawić sprawdzającym styl, a może potrzebujesz podpisu, który: z jakiegoś innego powodu (np. podklasy), ale tak naprawdę chcesz nieterminacji.)
źródło
Nie ma sytuacji, w której funkcja może osiągnąć swój koniec bez zwracania odpowiedniej wartości. Dlatego kompilator nie ma na co narzekać.
źródło
Visual Studio ma inteligentny silnik wykrywający, czy wpisałeś typ zwracany, a następnie powinien mieć instrukcję return w funkcji / metodzie.
Jak w PHP Twój typ zwrotu jest prawdziwy, jeśli nic nie zwróciłeś. kompilator dostaje 1, jeśli nic nie zostało zwrócone.
Od tego momentu
Kompilator wie, że chociaż sama instrukcja ma nieskończony charakter, więc nie należy jej brać pod uwagę. a kompilator php automatycznie się spełni, jeśli napiszesz warunek w wyrażeniu while.
Ale nie w przypadku VS zwróci ci błąd na stosie.
źródło
Twoja pętla while będzie działać wiecznie, a zatem nie wyjdzie na zewnątrz; będzie nadal działać. Dlatego zewnętrzna część while {} jest nieosiągalna i nie ma sensu pisać na piśmie, czy nie. Kompilator jest wystarczająco inteligentny, aby dowiedzieć się, która część jest osiągalna, a która nie.
Przykład:
Powyższy kod nie zostanie skompilowany, ponieważ może się zdarzyć, że wartość zmiennej x zostanie zmodyfikowana w treści pętli while. Dzięki temu można uzyskać dostęp do zewnętrznej części pętli while! Stąd kompilator wygeneruje błąd „nie znaleziono instrukcji return”.
Kompilator nie jest wystarczająco inteligentny (lub raczej leniwy;)), aby dowiedzieć się, czy wartość x zostanie zmodyfikowana, czy nie. Mam nadzieję, że to wszystko wyczyści.
źródło
int x = 1; while(x * 0 == 0) { ... }
była to nieskończona pętla, ale specyfikacja mówi, że kompilator powinien dokonywać dedukcji przepływu sterowania tylko wtedy, gdy wyrażenie pętli jest stałe , a specyfikacja definiuje wyrażenie stałe jako niezawierające zmiennych . Kompilator był zatem zbyt inteligentny . W C # 3 dostosowałem kompilator do specyfikacji i od tego czasu jest on celowo mniej inteligentny w zakresie tego rodzaju wyrażeń.„Dlaczego kompilator nawet nie ostrzega o zwróceniu czegoś? Albo dlaczego język miałby pozwalać na zastosowanie metody innej niż void posiadającej nieskończoną pętlę i niczego nie zwracającej?”.
Ten kod jest ważny również we wszystkich innych językach (prawdopodobnie oprócz Haskell!). Ponieważ pierwsze założenie jest takie, że „celowo” piszemy jakiś kod.
Są też sytuacje, w których ten kod może być całkowicie poprawny, np. Jeśli zamierzasz go używać jako wątku; lub jeśli zwraca a
Task<int>
, możesz wykonać sprawdzanie błędów na podstawie zwróconej wartości int - której nie należy zwracać.źródło
Mogę się mylić, ale niektóre debugery pozwalają na modyfikację zmiennych. Tutaj, podczas gdy x nie jest modyfikowany przez kod i będzie zoptymalizowany przez JIT, można zmodyfikować x na false, a metoda powinna coś zwrócić (jeśli taka możliwość jest dozwolona przez debugger C #).
źródło
Specyfika tego przypadku Java (które prawdopodobnie są bardzo podobne do przypadku C #) dotyczy sposobu, w jaki kompilator Java określa, czy metoda jest w stanie zwrócić.
W szczególności reguły są takie, że metoda z typem zwracanym nie może być w stanie zakończyć się normalnie i zamiast tego musi zawsze zakończyć się nagle (tutaj nagle, wskazując za pomocą instrukcji return lub wyjątku) zgodnie z JLS 8.4.7 .
Kompilator sprawdza, czy możliwe jest normalne zakończenie na podstawie reguł określonych w JLS 14.21 Nieosiągalne instrukcje ponieważ definiuje również reguły normalnego zakończenia.
W szczególności zasady dotyczące instrukcji nieosiągalnych stanowią szczególny przypadek tylko dla pętli, które mają zdefiniowane
true
stałe wyrażenie:Jeśli więc
while
instrukcja może się normalnie wypełnić , to konieczne jest podanie poniższej instrukcji return, ponieważ kod jest uznawany za osiągalny, a każdawhile
pętla bez osiągalnej instrukcji break lub stałejtrue
wyrażenia jest uważana za zdolną do ukończenia normalnie.Reguły te oznaczają, że twoja
while
instrukcja ze stałym prawdziwym wyrażeniem i bez a nigdy niebreak
jest uważana za zakończoną normalnie , więc żaden kod poniżej niej nigdy nie jest uważany za osiągalny . Koniec metody znajduje się poniżej pętli, a ponieważ wszystko poniżej pętli jest nieosiągalne, tak samo jest koniec metody, a zatem metoda nie może zakończyć się normalnie (tego szuka kompilator).if
instrukcje, z drugiej strony, nie mają specjalnego wyłączenia dotyczącego stałych wyrażeń, które są zapewnione pętlom.Porównać:
Z:
Powód tego rozróżnienia jest dość interesujący i wynika z chęci uwzględnienia flag kompilacji warunkowej, które nie powodują błędów kompilatora (z JLS):
Dlaczego warunkowa instrukcja break powoduje błąd kompilatora?
Jak podano w regułach osiągalności pętli, pętla while może również zakończyć się normalnie, jeśli zawiera osiągalną instrukcję break. Ponieważ zasady dotyczące osiągalności
if
oświadczenia następnie klauzuli nie biorą stanif
pod uwagę w ogóle, taka warunkowaif
oświadczenie jest następnie klauzula jest zawsze uważane za osiągalne.Jeśli
break
jest osiągalny, to kod po pętli jest ponownie uznawany za osiągalny. Ponieważ nie ma dostępnego kodu, który spowodowałby nagłe zakończenie po pętli, metoda jest następnie uważana za zdolną do ukończenia normalnie, a więc kompilator oznacza ją jako błąd.źródło