Jest kilka powodów, dla których używam wyrażenia „goto”, o których wiem (niektórzy już o tym mówili):
Czyste wyjście z funkcji
Często w funkcji możesz przydzielić zasoby i musisz wyjść w wielu miejscach. Programiści mogą uprościć swój kod, umieszczając kod czyszczenia zasobów na końcu funkcji, a wszystkie „punkty wyjścia” funkcji przejdą na etykietę czyszczenia. W ten sposób nie musisz pisać kodu czyszczenia w każdym „punkcie wyjścia” funkcji.
Wyjście z zagnieżdżonych pętli
Jeśli jesteś w zagnieżdżonej pętli i potrzebujesz wyrwać się ze wszystkich pętli, goto może uczynić to o wiele czystszym i prostszym niż instrukcje break i if-check.
Ulepszenia wydajności niskiego poziomu
Jest to poprawne tylko w kodzie krytycznym, ale instrukcje goto wykonują się bardzo szybko i mogą przyspieszyć działanie funkcji. Jest to jednak obosieczny miecz, ponieważ kompilator zwykle nie może zoptymalizować kodu zawierającego gotos.
Zauważ, że we wszystkich tych przykładach gotos są ograniczone do zakresu pojedynczej funkcji.
goto
goreturn
jest po prostu głupie. To nie jest „refaktoryzacja” niczego, to po prostu „zmiana nazwy”, tak aby ludzie, którzy dorastali wgoto
środowisku z tłumem (tj. Wszyscy z nas), czuli się lepiej, używając tego, co moralnie oznaczagoto
. Wolę widzieć pętlę tam, gdzie jej używam i widzieć trochęgoto
, co samo w sobie jest tylko narzędziem , niż widzieć, że ktoś przesunął pętlę gdzieś niezwiązany tylko po to, aby uniknąćgoto
.break
,continue
,return
są w zasadziegoto
tylko w ładnym opakowaniu.do{....}while(0)
ma być lepszym pomysłem niż goto, poza tym, że działa w Javie.Każdy, kto jest przeciwnikiem
goto
, bezpośrednio lub pośrednio, w GoTo Edsger Dijkstra za artykuł szkodliwy, uzasadniający swoje stanowisko. Szkoda, że artykuł Dijkstry nie ma praktycznie nic wspólnego ze sposobem, w jaki wgoto
dzisiejszych czasach używane są instrukcje, a zatem to, co mówi ten artykuł, ma niewiele lub nie ma zastosowania do współczesnej sceny programowania. Thegoto
nie- meme graniczy teraz na religię, aż do jego pism dyktowany z wysokości, jego arcykapłanów i unikanie (lub gorzej) postrzeganych heretyków.Umieśćmy tekst Dijkstry w kontekście, aby rzucić nieco światła na ten temat.
Kiedy Dijkstra napisał swój artykuł, popularnymi językami tamtych czasów były nieustrukturyzowane języki proceduralne, takie jak BASIC, FORTRAN (wcześniejsze dialekty) i różne języki asemblera. Ludzie często używający języków wyższego poziomu przeskakiwali całą bazę kodu w skręconych, wypaczonych wątkach wykonania, co dało początek określeniu „kod spaghetti”. Możesz to zobaczyć, przeskakując do klasycznej gry Trek napisanej przez Mike'a Mayfielda i próbując dowiedzieć się, jak to działa. Poświęć chwilę, aby to przemyśleć.
TO jest „niepohamowane użycie oświadczenia”, przeciwko któremu Dijkstra balansował w swoim artykule w 1968 roku. To środowisko, w którym żył, doprowadziło go do napisania tego artykułu. Możliwość skakania w dowolnym miejscu w kodzie w dowolnym momencie był tym, co krytykował i wymagał zatrzymania. Porównywanie tego do anemicznych mocy języka
goto
C lub innych bardziej nowoczesnych języków jest po prostu ryzykowne.Już słyszę wzniosłe śpiewy kultystów, gdy spotykają się z heretykiem. „Ale” - będą skandować - możesz bardzo utrudniać czytanie kodu
goto
w C. ” O tak? Możesz także bardzo utrudniać czytanie kodugoto
. Jak ten:Nie
goto
widać, więc musi być łatwy do odczytania, prawda? A może ten:Nie
goto
istnieje też. Dlatego musi być czytelny.Jaki jest sens tych przykładów? To nie funkcje językowe tworzą nieczytelny, niemożliwy do utrzymania kod. To nie jest składnia. Powodują to źli programiści. A źli programiści, jak widać w powyższym punkcie, mogą sprawić, że każda funkcja języka będzie nieczytelna i nie będzie używana. Jak
for
tamte pętle. (Możesz je zobaczyć, prawda?)Szczerze mówiąc, niektóre konstrukcje językowe są łatwiejsze do nadużyć niż inne. Jeśli jednak jesteś programistą C, przyjrzałbym się bliżej około 50% zastosowań na
#define
długo przed rozpoczęciem krucjaty przeciwkogoto
!Zatem dla tych, którzy próbowali czytać do tej pory, jest kilka kluczowych punktów do odnotowania.
goto
oświadczeń został napisany dla środowiska programistycznego, w którymgoto
było o wiele bardziej potencjalnie szkodliwe niż w większości współczesnych języków, które nie są asemblerem.goto
powodu jest tak racjonalne, jak powiedzenie „Próbowałem się kiedyś dobrze bawić, ale mi się nie podobało, więc teraz jestem temu przeciwny”.goto
instrukcji w kodzie, których nie można odpowiednio zastąpić innymi konstrukcjami.godo
obrzydliwość, w której zawszedo
używana jest fałszywa pętla,break
zamiast użyciagoto
. Często są one gorsze niż rozsądne użyciegoto
.źródło
goto
rzeczywistości zalety (jakie jest postawione pytanie)Ślepe przestrzeganie najlepszych praktyk nie jest najlepszą praktyką. Ideą unikania
goto
stwierdzeń jako podstawowej formy kontroli przepływu jest unikanie tworzenia nieczytelnego kodu spaghetti. Jeśli są stosowane oszczędnie w odpowiednich miejscach, mogą czasem być najprostszym i najczystszym sposobem wyrażenia pomysłu. Walter Bright, twórca kompilatora Zortech C ++ i języka programowania D, używa ich często, ale rozsądnie. Nawet zgoto
oświadczeniami jego kod jest nadal doskonale czytelny.Konkluzja: Unikanie
goto
w celu uniknięciagoto
jest bezcelowe. Tym, czego naprawdę chcesz uniknąć, jest tworzenie nieczytelnego kodu. Jeśli twój-goto
obciążony kod jest czytelny, to nie ma w tym nic złego.źródło
Ponieważ
goto
utrudnia rozumowanie na temat przepływu programu 1 (alias. „Kod spaghetti”),goto
jest zwykle używany tylko w celu kompensacji brakujących funkcji: użyciegoto
może być w rzeczywistości dopuszczalne, ale tylko wtedy, gdy język nie oferuje bardziej strukturalnego wariantu do uzyskania ten sam cel. Weźmy przykład Wątpliwości:Jest to prawda - ale tylko wtedy, gdy język nie pozwala na obsługę wyjątków strukturalnych z kodem czyszczenia (takim jak RAII lub
finally
), który wykonuje to samo zadanie lepiej (ponieważ jest specjalnie zbudowany do tego), lub gdy jest ku temu dobry powód zastosować ustrukturyzowaną obsługę wyjątków (ale nigdy nie będziesz mieć tej sprawy, chyba że na bardzo niskim poziomie).W większości innych języków jedynym dopuszczalnym zastosowaniem
goto
jest wyjście z zagnieżdżonych pętli. I nawet tam prawie zawsze lepiej jest podnieść zewnętrzną pętlę do własnej metody i zastosowaćreturn
zamiast niej.Poza tym
goto
jest znakiem, że w danym fragmencie kodu nie ma wystarczającej ilości myśli.1 Współczesne języki, które obsługują
goto
implementują pewne ograniczenia (np.goto
Nie mogą przeskakiwać do funkcji lub z nich wychodzić), ale problem zasadniczo pozostaje ten sam.Nawiasem mówiąc, to samo dotyczy oczywiście innych funkcji językowych, w szczególności wyjątków. Zazwyczaj obowiązują ścisłe reguły korzystania z tych funkcji tylko tam, gdzie jest to wskazane, takie jak reguła, aby nie używać wyjątków do kontrolowania nietypowego przebiegu programu.
źródło
finally
? Więc stosowanie wyjątków dla rzeczy innych niż obsługa błędów jest dobre, ale używaniegoto
jest złe? Myślę, że wyjątki są dość trafnie nazwane.Jest jedna rzecz, która zawsze jest gorsza niż
goto's
; dziwne użycie innych operatorów przepływu programów, aby uniknąć goto:Przykłady:
źródło
do{}while(false)
Myślę, że można to uznać za idiomatyczne. Nie możesz się nie zgodzić: Dgoto after_do_block;
bez mówienia tego. W przeciwnym razie ... „pętla”, która działa dokładnie raz? Nazwałbym to nadużyciem struktur kontrolnych.#define
wiele jest, wiele razy znacznie gorszych niż używaniegoto
raz na jakiś czas: DW instrukcji C # switch nie można pozwolić na awarię . Tak goto jest używana do kontroli transferu do określonej etykiety switch-case lub domyślnie etykiecie.
Na przykład:
Edycja: Istnieje jeden wyjątek od reguły „bez awarii”. Awaria jest dozwolona, jeśli instrukcja case nie ma kodu.
źródło
goto case 5:
kiedy jesteś w przypadku 1). Wygląda na to, że odpowiedź Konrada Rudolfa jest tutaj poprawna:goto
kompensuje brakującą cechę (i jest mniej jasna niż byłaby prawdziwa cecha). Jeśli to, czego tak naprawdę chcemy, to przejście awaryjne, być może najlepszym rozwiązaniem domyślnym byłby brak wyjścia awaryjnego, ale coś w rodzajucontinue
wyraźnego żądania.#ifdef TONGUE_IN_CHEEK
Perl ma taki,
goto
który pozwala na implementację ogonów biedaka. :-P#endif
Ok, tak, że nie ma nic wspólnego z C na
goto
. Mówiąc poważniej, zgadzam się z innymi komentarzami dotyczącymi używaniagoto
do czyszczenia lub wdrażania urządzenia Duffa itp. Chodzi o używanie, a nie nadużywanie.(Ten sam komentarz może dotyczyć
longjmp
wyjątkówcall/cc
i tym podobnych - mają one uzasadnione zastosowania, ale mogą być łatwo nadużywane. Na przykład, rzucając wyjątek wyłącznie w celu uniknięcia głęboko zagnieżdżonej struktury kontroli, w całkowicie nietypowych okolicznościach .)źródło
Przez lata napisałem ponad kilka wierszy języka asemblera. Ostatecznie każdy język wysokiego poziomu kompiluje się do gotos. Ok, nazywaj je „gałęziami”, „skokami” lub czymkolwiek innym, ale są gotowi. Czy ktoś może napisać asembler bez goto?
Teraz możesz wskazać programistom z Fortran, C lub BASIC, że prowadzenie zamieszek za pomocą gotos to przepis na bolońskie spaghetti. Odpowiedzią nie jest jednak unikanie ich, ale ostrożne ich używanie.
Noża można użyć do przygotowania jedzenia, uwolnienia kogoś lub zabicia kogoś. Czy robimy to bez noży przez strach przed nimi? Podobnie goto: używane niedbale utrudnia, używane ostrożnie pomaga.
źródło
Spójrz na Kiedy używać Goto podczas programowania w C :
Co mam na myśli? Możesz mieć kod, który wygląda następująco:
Jest to w porządku, dopóki nie uświadomisz sobie, że musisz zmienić kod czyszczenia. Następnie musisz przejść i wprowadzić 4 zmiany. Teraz możesz zdecydować, że możesz po prostu zamknąć wszystkie porządki w jedną funkcję; to nie jest zły pomysł. Ale to oznacza, że musisz uważać na wskaźniki - jeśli planujesz zwolnić wskaźnik w funkcji czyszczenia, nie ma sposobu, aby ustawić go tak, aby wskazywał na NULL, chyba że podasz wskaźnik do wskaźnika. W wielu przypadkach i tak nie będziesz ponownie używać tego wskaźnika, więc może to nie stanowić poważnego problemu. Z drugiej strony, jeśli dodasz nowy wskaźnik, uchwyt pliku lub inną rzecz wymagającą czyszczenia, musisz ponownie zmienić funkcję czyszczenia; i wtedy musisz zmienić argumenty na tę funkcję.
Przy użyciu
goto
będzieKorzyścią jest to, że Twój kod po zakończeniu ma dostęp do wszystkiego, co będzie potrzebne do przeprowadzenia czyszczenia, i udało ci się znacznie zmniejszyć liczbę punktów zmiany. Kolejną korzyścią jest to, że przeszedłeś z posiadania wielu punktów wyjścia dla swojej funkcji do jednego; nie ma szans, że przypadkowo wrócisz z funkcji bez sprzątania.
Co więcej, ponieważ goto jest używane tylko do przeskakiwania do jednego punktu, nie jest tak, że tworzysz masę kodu spaghetti skaczącego tam iz powrotem, próbując symulować wywołania funkcji. Goto raczej pomaga napisać bardziej uporządkowany kod.
Jednym słowem,
goto
należy zawsze używać go oszczędnie i w ostateczności - ale jest na to czas i miejsce. Pytanie nie powinno brzmieć „musisz go użyć”, ale „czy to najlepszy wybór”, aby go użyć.źródło
Jednym z powodów, dla których goto jest zły, oprócz stylu kodowania, jest to, że można go używać do tworzenia nakładających się , ale nie zagnieżdżonych pętli:
Stworzyłoby to dziwaczną, ale być może legalną strukturę kontroli, w której możliwa jest sekwencja (a, b, c, b, a, b, a, b, ...), co czyni hakerów kompilatora nieszczęśliwymi. Najwyraźniej istnieje wiele sprytnych sztuczek optymalizacyjnych, które polegają na tym, że taka struktura nie występuje. (Powinienem sprawdzić moją kopię książki o smokach ...) Może to (przy użyciu niektórych kompilatorów) spowodować, że nie zostaną wykonane inne optymalizacje dla kodu zawierającego
goto
s.Może to być przydatne, jeśli wiesz, że „och, nawiasem mówiąc”, zdarza się, aby przekonać kompilator do emitowania szybszego kodu. Osobiście wolałbym spróbować wyjaśnić kompilatorowi, co jest prawdopodobne, a co nie, zanim użyję sztuczki takiej jak goto, ale prawdopodobnie mógłbym również spróbować
goto
przed zhakowaniem asemblera.źródło
goto
jest przydatny, jest to, że pozwala on tworzyć takie pętle, które w przeciwnym razie wymagałyby wielu logicznych zniekształceń. Dalej argumentowałbym, że jeśli optymalizator nie wie, jak to przepisać, to dobrze . Pętli takiej nie należy wykonywać dla wydajności lub czytelności, ale ponieważ jest to dokładnie kolejność, w której rzeczy muszą się dziać. W takim przypadku nie chciałbym, żeby optymalizator się z nim kręcił.Zabawne jest dla mnie to, że niektórzy posunęli się do podania listy przypadków, w których goto jest dopuszczalne, mówiąc, że wszystkie inne zastosowania są niedopuszczalne. Czy naprawdę uważasz, że znasz każdy przypadek, w którym goto jest najlepszym wyborem do wyrażenia algorytmu?
Aby to zilustrować, dam wam przykład, którego nikt tutaj jeszcze nie pokazał:
Dzisiaj pisałem kod do wstawiania elementu do tablicy mieszającej. Tabela skrótów jest pamięcią podręczną poprzednich obliczeń, które można dowolnie nadpisywać (wpływając na wydajność, ale nie na poprawność).
Każde wiadro tabeli skrótów ma 4 miejsca i mam kilka kryteriów, które decydują, który element zastąpić, gdy wiadro jest pełne. W tej chwili oznacza to wykonanie do trzech przejść przez wiadro, takie jak to:
Gdybym nie użył goto, jak wyglądałby ten kod?
Coś takiego:
Gdyby dodać więcej przejść, wyglądałoby to coraz gorzej, podczas gdy wersja z goto utrzymuje ten sam poziom wcięcia przez cały czas i unika stosowania fałszywych instrukcji if, których wynik wynika z wykonania poprzedniej pętli.
Więc jest inny przypadek, w którym goto sprawia, że kod jest czystszy i łatwiejszy do napisania i zrozumienia ... Jestem pewien, że jest ich o wiele więcej, więc nie udawaj, że znasz wszystkie przypadki, w których goto jest użyteczny, odrzucając wszelkie dobre, których nie możesz o tym nie myślę.
źródło
goto
każda funkcja była na tym samym poziomie abstrakcji. To, czego unika,goto
to bonus.container::iterator it = slot_p.find(hash_key); if (it != slot_p.end()) it->overwrite(hash_key); else it = slot_p.find_first_empty();
mniej więcej tak : Uważam, że tego rodzaju programowanie jest znacznie łatwiejsze do odczytania. Każda funkcja w tym przypadku może być zapisana jako funkcja czysta, o której łatwiej jest myśleć. Główna funkcja wyjaśnia teraz, co robi kod, po nazwie funkcji, a następnie, jeśli chcesz, możesz spojrzeć na ich definicje, aby dowiedzieć się, jak to robi.Reguła z goto, której używamy, jest taka, że goto jest w porządku, aby przeskoczyć do przodu do jednego punktu czyszczenia wyjścia w funkcji. W naprawdę złożonych funkcjach rozluźniamy tę zasadę, aby umożliwić innym skok do przodu. W obu przypadkach unikamy głęboko zagnieżdżonych instrukcji, które często występują podczas sprawdzania kodu błędu, co pomaga w czytelności i konserwacji.
źródło
Najbardziej przemyślaną i dokładną dyskusją na temat instrukcji goto, ich uzasadnionych zastosowań i alternatywnych konstrukcji, które można stosować zamiast „cnotliwych instrukcji goto”, ale które można nadużywać równie łatwo, jak instrukcje goto, jest artykuł Donalda Knutha „ Programowanie strukturalne za pomocą instrukcji goto ” , w grudniowych obliczeniach ankietowych z 1974 r. (tom 6, nr 4, s. 261–301).
Nic dziwnego, że niektóre aspekty tego 39-letniego artykułu są datowane: wzrosty mocy obliczeniowej rzędu rzędów sprawiają, że niektóre ulepszenia wydajności Knutha są niezauważalne w przypadku problemów o średniej wielkości, a od tego czasu opracowano nowe konstrukcje języka programowania. (Na przykład bloki try-catch zajmują Konstrukcję Zahna, chociaż są rzadko używane w ten sposób.) Ale Knuth obejmuje wszystkie strony argumentu i powinien zostać przeczytany, zanim ktokolwiek ponownie podejmie ten problem.
źródło
W module Perla od czasu do czasu chcesz tworzyć podprogramy lub zamknięcia w locie. Chodzi o to, że po utworzeniu podprogramu, jak się do niego dostać. Możesz to po prostu nazwać, ale jeśli podprogram
caller()
się zastosuje, nie będzie tak pomocny, jak mógłby być. To gdziegoto &subroutine
zmiana może być pomocna.Oto szybki przykład:
Możesz także użyć tej formy,
goto
aby zapewnić podstawową formę optymalizacji przywoławczej.(W wersji 16 Perla 5 lepiej byłoby napisać jako
goto __SUB__;
)Istnieje moduł, który zaimportuje
tail
modyfikator i taki, który zaimportuje,recur
jeśli nie lubisz używać tej formygoto
.Większość innych powodów do użycia
goto
lepiej jest wykonać przy użyciu innych słów kluczowych.Jak
redo
na przykład trochę kodu:Lub przechodząc do
last
odrobiny kodu z wielu miejsc:źródło
C nie ma podziału wielopoziomowego / oznaczonego i nie wszystkie przepływy sterowania można łatwo modelować za pomocą iteracji i operacji podstawowych C. muszę przejść długą drogę, by naprawić te wady.
Czasami łatwiej jest użyć pewnego rodzaju zmiennej flagowej, aby wywołać rodzaj pseudo-wielopoziomowej przerwy, ale nie zawsze jest ona lepsza od goto (przynajmniej goto pozwala łatwo określić, dokąd idzie kontrola, w przeciwieństwie do zmiennej flagowej ), a czasem po prostu nie chcesz płacić ceny wykonania flag / innych wygięć, aby uniknąć goto.
libavcodec to wrażliwy na wydajność fragment kodu. Bezpośrednie wyrażenie przepływu sterowania jest prawdopodobnie priorytetem, ponieważ będzie działało lepiej.
źródło
Równie dobrze nikt nigdy nie wdrożył instrukcji „COME FROM” ....
źródło
Uważam, że do {} podczas (fałszywego) użytkowania jest całkowicie obrzydliwe. Można sobie wyobrazić, że może mnie to przekonać w niektórych dziwnych przypadkach, ale nigdy nie jest to czysty i rozsądny kod.
Jeśli musisz wykonać taką pętlę, dlaczego nie wyjaśnić zależności od zmiennej flagi?
źródło
/*empty*/
byćstepfailed = 1
? W każdym razie, jak to jest lepsze niżdo{}while(0)
? W obu przypadkach musiszbreak
wyjść z tego (lub w swoimstepfailed = 1; continue;
). Nie wydaje mi się to konieczne.1) Najczęstszym zastosowaniem goto, o którym wiem, jest emulacja obsługi wyjątków w językach, które go nie oferują, a mianowicie w C. (Kod podany powyżej przez Nuclear jest właśnie taki.) Spójrz na kod źródłowy Linuksa, a ty ' Zobaczę, jak w ten sposób wykorzystano gotill bazillion; według szybkiej ankiety przeprowadzonej w 2013 roku: około 100 000 gotów w kodzie systemu Linux: http://blog.regehr.org/archives/894 . Korzystanie z Goto jest nawet wspomniane w przewodniku po stylach kodowania Linuksa: https://www.kernel.org/doc/Documentation/CodingStyle . Podobnie jak emulowanie programowania obiektowego za pomocą struktur wypełnionych wskaźnikami funkcyjnymi, goto ma swoje miejsce w programowaniu C. Więc kto ma rację: Dijkstra lub Linus (i wszystkie kodery jądra Linuksa)? Zasadniczo jest to teoria a praktyka.
Istnieje jednak zwykła gotcha na brak obsługi na poziomie kompilatora i sprawdzanie typowych konstrukcji / wzorców: łatwiej jest ich używać nieprawidłowo i wprowadzać błędy bez sprawdzania czasu kompilacji. Windows i Visual C ++, ale w trybie C oferują obsługę wyjątków przez SEH / VEH z tego właśnie powodu: wyjątki są przydatne nawet poza językami OOP, tj. W języku proceduralnym. Ale kompilator nie zawsze może zapisać twój bekon, nawet jeśli oferuje wsparcie składniowe dla wyjątków w języku. Rozważmy jako przykład tego drugiego przypadku słynny błąd „goto fail” Apple SSL, który właśnie zduplikował jedno goto z katastrofalnymi konsekwencjami ( https://www.imperialviolet.org/2014/02/22/applebug.html ):
Możesz mieć dokładnie ten sam błąd, korzystając z wyjątków obsługiwanych przez kompilator, np. W C ++:
Oba warianty błędu można jednak uniknąć, jeśli kompilator przeanalizuje i ostrzeże o nieosiągalnym kodzie. Na przykład kompilacja z Visual C ++ na poziomie ostrzegawczym / W4 znajdzie błąd w obu przypadkach. Na przykład Java zabrania dostępu do kodu nieosiągalnego (tam, gdzie można go znaleźć!) Z całkiem dobrego powodu: prawdopodobnie jest to błąd w kodzie przeciętnego Joe. Dopóki konstrukcja goto nie zezwala na cele, których kompilator nie jest w stanie łatwo zrozumieć, takie jak gotos do obliczonych adresów (**), kompilatorowi nie jest trudniej znaleźć nieosiągalny kod wewnątrz funkcji z gotos niż użycie Dijkstry -akceptowany kod.
(**) Przypis: Gotos do obliczonych numerów linii jest możliwy w niektórych wersjach Basic, np. GOTO 10 * x, gdzie x jest zmienną. Raczej myląco, w Fortranie „obliczone goto” odnosi się do konstrukcji, która jest równoważna instrukcji switch w C. Standard C nie zezwala na obliczone goto w języku, ale tylko gotos na statycznie / składniowo zadeklarowane etykiety. GNU C ma jednak rozszerzenie umożliwiające uzyskanie adresu etykiety (operator unary, operator prefiksu &&), a także pozwala na przejście do zmiennej typu void *. Zobacz https://gcc.gnu.org/onlinedocs/gcc/Labels-as-Values.html, aby uzyskać więcej informacji na temat tego niejasnego podtematu. Reszta tego postu nie dotyczy tej niejasnej funkcji GNU C.
Gotowe standardowe C (tj. Nie obliczone) zwykle nie są przyczyną, dla której nie można znaleźć nieosiągalnego kodu w czasie kompilacji. Typowym powodem jest następujący kod logiczny. Dany
Kompilatorowi równie trudno jest znaleźć nieosiągalny kod w jednej z następujących 3 konstrukcji:
(Przepraszam za mój styl kodowania związany z nawiasami klamrowymi, ale starałem się zachować jak najmniejsze przykłady).
Visual C ++ / W4 (nawet z / Ox) nie może znaleźć nieosiągalnego kodu w żadnym z nich, a jak zapewne wiesz, problem znalezienia nieosiągalnego kodu jest ogólnie nierozstrzygalny. (Jeśli mi w to nie wierzysz: https://www.cl.cam.ac.uk/teaching/2006/OptComp/slides/lecture02.pdf )
Jako pokrewny problem można użyć C goto do emulacji wyjątków tylko w treści funkcji. Standardowa biblioteka C oferuje parę funkcji setjmp () i longjmp () do emulacji nielokalnych wyjść / wyjątków, ale mają one poważne wady w porównaniu z innymi językami. Artykuł w Wikipedii http://en.wikipedia.org/wiki/Setjmp.h dość dobrze wyjaśnia ten ostatni problem. Ta para funkcji działa również w systemie Windows ( http://msdn.microsoft.com/en-us/library/yz2ez4as.aspx ), ale prawie nikt ich tam nie używa, ponieważ SEH / VEH jest lepszy. Nawet w Uniksie myślę, że setjmp i longjmp są bardzo rzadko używane.
2) Myślę, że drugim najczęstszym zastosowaniem goto w C jest implementacja wielopoziomowej przerwy lub kontynuacji wielopoziomowej, co jest również dość kontrowersyjnym przypadkiem użycia. Przypomnij sobie, że Java nie zezwala na etykietę goto, ale umożliwia przerwanie etykiety lub kontynuowanie etykiety. Według http://www.oracle.com/technetwork/java/simple-142616.html jest to w rzeczywistości najczęstszy przypadek użycia gotos w C (90% mówią), ale z mojego subiektywnego doświadczenia wynika, że kod systemu ma tendencję do częściej używać gotos do obsługi błędów. Być może w kodzie naukowym lub gdy system operacyjny oferuje obsługę wyjątków (Windows), wyjścia wielopoziomowe są dominującym przypadkiem użycia. Tak naprawdę nie podają żadnych szczegółów dotyczących kontekstu ankiety.
Zmodyfikowano w celu dodania: okazuje się, że te dwa wzorce użycia znajdują się w książce C Kernighana i Ritchie, na stronie 60 (w zależności od wydania). Należy również zauważyć, że oba przypadki użycia dotyczą tylko gotowych zmian. Okazuje się, że edycja MISRA C 2012 (w przeciwieństwie do edycji z 2004 r.) Pozwala teraz na gotos, o ile są one tylko nowymi.
źródło
Niektórzy twierdzą, że nie ma powodu, aby goto w C ++. Niektórzy twierdzą, że w 99% przypadków istnieją lepsze alternatywy. To nie jest rozumowanie, tylko irracjonalne wrażenia. Oto solidny przykład, w którym goto prowadzi do ładnego kodu, czegoś w rodzaju ulepszonej pętli do-while:
Porównaj to z kodem wolnym od goto:
Widzę te różnice:
{}
potrzebny jest zagnieżdżony blok (choćdo {...} while
wygląda bardziej znajomo)loop
potrzebna jest dodatkowa zmienna, używana w czterech miejscachloop
loop
nie posiada żadnych danych, po prostu steruje przepływem wykonania, która jest mniej zrozumiały niż prosty etykiecieJest inny przykład
Pozbądźmy się teraz „złego” goto:
Widzisz, że jest to ten sam rodzaj używania goto, ma dobrze ustrukturyzowany wzorzec i nie jest goto do przodu tak często, jak tylko jest to zalecane. Na pewno chcesz uniknąć takiego „inteligentnego” kodu:
Chodzi o to, że goto można łatwo nadużywać, ale nie można go winić. Zauważ, że etykieta ma zakres funkcji w C ++, więc nie zanieczyszcza zasięgu globalnego, tak jak w czystym asemblerze, w którym nakładające się pętle mają swoje miejsce i są bardzo powszechne - jak w poniższym kodzie dla 8051, gdzie 7-segmentowy wyświetlacz jest podłączony do P1. Program zapętla segment błyskawicy wokół:
Jest jeszcze jedna zaleta: goto może służyć jako nazwane pętle, warunki i inne przepływy:
Lub możesz użyć równoważnego goto z wcięciem, więc nie potrzebujesz komentarza, jeśli mądrze wybierzesz nazwę etykiety:
źródło
W Perlu: użycie etykiety do „goto” z pętli - użycie instrukcji „last”, która jest podobna do break.
Pozwala to na lepszą kontrolę nad zagnieżdżonymi pętlami.
Obsługiwana jest również tradycyjna etykieta goto , ale nie jestem pewien, czy istnieje zbyt wiele przypadków, w których jest to jedyny sposób na osiągnięcie tego, czego chcesz - w większości przypadków wystarczą podprogramy i pętle.
źródło
goto &subroutine
. Który uruchamia podprogram z bieżącym @_, jednocześnie zastępując bieżący podprogram w stosie.Problem z „goto” i najważniejszym argumentem ruchu „programowanie bez goto” polega na tym, że jeśli użyjesz go zbyt często, twój kod, chociaż może zachowywać się poprawnie, staje się nieczytelny, niemożliwy do utrzymania, nie można go zobaczyć itp. W 99,99% przypadki „goto” prowadzą do kodu spaghetti. Osobiście nie mogę wymyślić żadnego dobrego powodu, dla którego miałbym używać „goto”.
źródło
goto
). Wykorzystanie @ cschol jest podobne: chociaż może nie projektuje teraz języka, zasadniczo ocenia wysiłek projektanta.goto
z wyjątkiem sytuacji, w których spowodowałoby to istnienie zmiennych, może być tańsze niż próba obsługi każdego rodzaju struktury kontroli, której ktoś może potrzebować. Pisanie kodugoto
może nie być tak przyjemne jak używanie jakiejś innej struktury, ale możliwość pisania takiego kodugoto
pomoże uniknąć „dziur w ekspresji” - konstrukcjach, dla których język nie jest w stanie napisać wydajnego kodu.goto
na stronie przeglądu kodu, wyeliminowaniegoto
znacznie upraszcza logikę kodu.Z GOTO można oczywiście korzystać, ale jest jedna ważniejsza rzecz niż styl kodu, lub jeśli kod jest czytelny lub nieczytelny, musisz o tym pamiętać: kod w nim może nie być tak solidny, jak ty myśleć .
Na przykład spójrz na następujące dwa fragmenty kodu:
Odpowiednik kodu z GOTO
Najpierw uważamy, że wynikiem obu bitów kodu będzie „Wartość A: 0” (oczywiście wykonanie bez równoległości)
To nie jest poprawne: w pierwszej próbce A będzie zawsze wynosić 0, ale w drugiej próbce (z instrukcją GOTO) A może nie być 0. Dlaczego?
Powodem jest to, że z innego punktu programu mogę wstawić
GOTO FINAL
bez kontrolowania wartości A.Ten przykład jest bardzo oczywisty, ale w miarę jak programy się komplikują, trudność z dostrzeżeniem tego rodzaju rzeczy wzrasta.
Powiązany materiał można znaleźć w słynnym artykule pana Dijkstry „Sprawa przeciwko oświadczeniu GO TO”
źródło
Używam goto w następującym przypadku: w razie potrzeby, aby powrócić z funkcji w różnych miejscach, a przed zwrotem należy wykonać pewne niezainicjowanie:
wersja nie goto:
wersja goto:
Druga wersja ułatwia, gdy trzeba coś zmienić w instrukcjach dezalokacji (każda jest używana raz w kodzie), i zmniejsza szansę na pominięcie któregoś z nich podczas dodawania nowej gałęzi. Przeniesienie ich w funkcji nie pomoże tutaj, ponieważ zwolnienie można wykonać na różnych „poziomach”.
źródło
finally
bloki w języku C #finally
). Alternatywnie użyjgoto
s, ale do wspólnego punktu wyjścia, który zawsze wykonuje wszystkie czyszczenie. Ale każda metoda czyszczenia może obsłużyć wartość zerową lub już wyczyszczoną lub jest chroniona przez test warunkowy, więc jest pomijana, gdy nie jest odpowiednia.goto
s, że wszystkie idą do tego samego punktu wyjścia, który ma tę samą logikę (która, jak mówisz, wymaga dodatkowego, jeśli na zasób). Ale nieważne, kiedy używaszC
masz rację - bez względu na powód, dla którego kod znajduje się w C, prawie na pewno kompromis faworyzuje najbardziej „bezpośredni” kod. (Moja sugestia radzi sobie ze złożonymi sytuacjami, w których dowolny zasób mógł zostać przydzielony lub nie. Ale tak, przesadzenie w tym przypadku.)Edsger Dijkstra, informatyk, który miał duży wkład w tę dziedzinę, słynął również z krytyki użycia GoTo. W Wikipedii znajduje się krótki artykuł na temat jego argumentacji .
źródło
Od czasu do czasu przydaje się do przetwarzania ciągów znaków.
Wyobraź sobie coś takiego jak ten przykładowy printf:
Możesz to zmienić
goto handle_literal
do wywołania funkcji, ale jeśli modyfikuje on kilka różnych zmiennych lokalnych, będziesz musiał przekazać odniesienia do każdej z nich, chyba że Twój język obsługuje zmienne zamknięcia. Nadal będziesz musiał użyćcontinue
instrukcji (która jest prawdopodobnie formą goto) po wywołaniu, aby uzyskać tę samą semantykę, jeśli twoja logika sprawia, że inna sprawa nie działa.Gotos używałem również ostrożnie w leksykonach, zazwyczaj w podobnych przypadkach. Nie potrzebujesz ich przez większość czasu, ale fajnie jest mieć je w tych dziwnych przypadkach.
źródło