Do jakiego rodzaju błędów prowadzą instrukcje „goto”? Czy są jakieś historycznie znaczące przykłady?

103

Rozumiem, że oprócz przełamywania pętli zagnieżdżonych w pętlach; gotooświadczenie omijane i drwił jako błąd skłonnej stylu programowania, aby nigdy nie być użyte.

XKCD Alt Text: „Neal Stephenson uważa, że ​​fajnie jest nazywać jego etykiety„ dengo ””.
Zobacz oryginalny komiks na stronie: http://xkcd.com/292/

Ponieważ nauczyłem się tego wcześnie; Naprawdę nie mam wglądu ani doświadczenia na temat tego, do jakiego rodzaju błędów gotofaktycznie prowadzi. O czym tu mówimy:

  • Niestabilność?
  • Nieusuwalny lub nieczytelny kod?
  • Luki w zabezpieczeniach?
  • Coś zupełnie innego?

Do jakiego rodzaju błędów faktycznie prowadzą instrukcje „goto”? Czy są jakieś historycznie znaczące przykłady?

Akiva
źródło
33
Czy przeczytałeś oryginalny krótki artykuł Dijkstry na ten temat? (To jest pytanie Tak / Nie, a każda odpowiedź inna niż jednoznaczne „tak” jest „nie”.)
John R. Strohm
2
Zakładam, że wyjście alarmowe ma miejsce, gdy wydarzy się coś katastrofalnego. skąd nigdy nie wrócisz. Jak awaria zasilania, znikające urządzenia lub pliki. Niektóre rodzaje „na błąd goto”… Ale myślę, że większość „prawie katastroficznych” błędów można teraz rozwiązać za pomocą konstrukcji „try-catch”.
Lenne
13
Nikt jeszcze nie wspomniał o obliczonym GOTO. Kiedy ziemia była młoda, BASIC miał numery linii. Możesz napisać: 100 N = A * 100; GOTO N. Spróbuj debugować to :)
mcottle
7
@ JohnR.Strohm Przeczytałem artykuł Dijkstry. Został napisany w 1968 roku i sugeruje, że potrzeba gotów może zostać zmniejszona w językach, które zawierają tak zaawansowane funkcje, jak instrukcje i funkcje przełączników. Został napisany w odpowiedzi na języki, w których goto było podstawową metodą kontroli przepływu i można go było użyć do przejścia do dowolnego punktu programu. Podczas gdy w językach, które zostały opracowane po napisaniu tego artykułu, np. C, goto może przeskakiwać tylko do miejsc w tej samej ramce stosu i jest zwykle używany tylko wtedy, gdy inne opcje nie działają dobrze. (ciąg dalszy ...)
Ray
10
(... ciąg dalszy) Jego punkty były wówczas ważne, ale nie są już istotne, niezależnie od tego, czy goto jest dobrym pomysłem, czy nie. Jeśli chcemy argumentować w ten czy inny sposób, należy przedstawić argumenty dotyczące współczesnych języków. Nawiasem mówiąc, czy przeczytałeś „Programowanie strukturalne Knouda z goto”?
Ray

Odpowiedzi:

2

Oto sposób spojrzenia na to, czego jeszcze nie widziałem w innych odpowiedziach.

Chodzi o zakres. Jednym z głównych filarów dobrej praktyki programowania jest utrzymanie ścisłego zasięgu. Potrzebujesz ścisłego zasięgu, ponieważ brakuje ci mentalnej zdolności do nadzorowania i rozumienia więcej niż tylko kilku logicznych kroków. Tworzysz więc małe bloki (z których każdy staje się „jedną rzeczą”), a następnie budujesz z nich, aby tworzyć większe bloki, które stają się jedną rzeczą i tak dalej. Dzięki temu wszystko jest łatwe do zarządzania i zrozumiałe.

Goto skutecznie zwiększa zakres twojej logiki do całego programu. To z pewnością pokona Twój mózg w przypadku wszystkich programów oprócz najmniejszych obejmujących tylko kilka linii.

Więc nie będziesz już w stanie stwierdzić, czy popełniłeś błąd, ponieważ jest po prostu zbyt wiele do zaakceptowania i sprawdzenia swojej biednej małej główki. To jest prawdziwy problem, błędy są tylko prawdopodobnym rezultatem.

Martin Maat
źródło
Nielokalne goto?
Deduplicator
thebrain.mcgill.ca/flash/capsules/experience_jaune03.html << Ludzki umysł może śledzić średnio 7 rzeczy na raz. Twoje uzasadnienie jest zgodne z tym ograniczeniem, ponieważ rozszerzenie zakresu doda wiele innych rzeczy, które należy śledzić. Zatem typy błędów, które zostaną wprowadzone, są prawdopodobnie zaniedbaniem i zapomnieniem. Proponujesz również strategię łagodzenia i ogólnie dobrą praktykę polegającą na „ograniczeniu zakresu”. Jako taki akceptuję tę odpowiedź. Dobra robota Martin.
Akiva
1
Jakie to jest świetne?! Spośród ponad 200 głosów, wygrywając zaledwie 1!
Martin Maat
68

Nie chodzi o to, że gotosamo w sobie jest złe. (W końcu każda instrukcja skoku w komputerze jest goto.) Problem polega na tym, że istnieje ludzki styl programowania, który poprzedza programowanie strukturalne, co można nazwać programowaniem „schematu blokowego”.

W programowaniu schematów blokowych (którego nauczyli się ludzie z mojego pokolenia i który był używany w programie księżycowym Apollo) tworzysz schemat z blokami do wykonywania instrukcji i diamentami do podejmowania decyzji, i możesz połączyć je liniami, które biegną wszędzie. (Tak zwany „kod spaghetti”.)

Problem z kodem spaghetti polega na tym, że ty, programista, „wiesz”, że miał rację, ale jak możesz go udowodnić sobie lub komukolwiek innemu? W rzeczywistości może to mieć potencjalne złe zachowanie, a twoja wiedza, że ​​zawsze jest poprawna, może być błędna.

Wraz z programowaniem strukturalnym wprowadzono bloki początkowe, na razie, jeśli-jeszcze, i tak dalej. Zaletą tego było to, że nadal można w nich zrobić cokolwiek, ale jeśli będziesz w ogóle ostrożny, możesz mieć pewność, że kod jest poprawny.

Oczywiście ludzie wciąż mogą pisać kod spaghetti, nawet bez niego goto. Powszechną metodą jest napisanie a while(...) switch( iState ){..., w którym różne przypadki ustawiają iStateróżne wartości. W rzeczywistości w C lub C ++ można napisać makro, aby to zrobić, i nazwać to GOTO, więc powiedzenie, że nie używasz, gotojest różnicą bez różnicy.

Jako przykład tego, w jaki sposób sprawdzanie kodu może wykluczać nieograniczone goto, dawno temu natknąłem się na strukturę sterowania przydatną w dynamicznie zmieniających się interfejsach użytkownika. Nazwałem to wykonaniem różnicowym . Jest w pełni Turing uniwersalny, ale jego poprawność dowód zależy od czystego programowania strukturalnego - brak goto, return, continue, break, lub wyjątki.

wprowadź opis zdjęcia tutaj

Mike Dunlavey
źródło
49
Prawidłowości nie można udowodnić, z wyjątkiem najbardziej trywialnych programów, nawet jeśli do ich pisania używa się programowania strukturalnego. Możesz tylko zwiększyć swój poziom zaufania; programowanie strukturalne to osiąga.
Robert Harvey
23
@MikeDunlavey: Powiązane: „Strzeż się błędów w powyższym kodzie; udowodniłem tylko, że jest poprawny, a nie wypróbowałem”. staff.fnwi.uva.nl/p.vanemdeboas/knuthnote.pdf
Kaczka
26
@RobertHarvey Nie do końca jest prawdą, że dowody poprawności są praktyczne tylko w przypadku trywialnych programów. Niezbędne są jednak bardziej specjalistyczne narzędzia niż programowanie strukturalne.
Mike Haskel
18
@MSalters: To projekt badawczy. Celem takiego projektu badawczego jest wykazanie, że mogliby napisać zweryfikowany kompilator, gdyby mieli odpowiednie zasoby. Jeśli spojrzysz na ich obecną pracę, zobaczysz, że po prostu nie są zainteresowani obsługą późniejszych wersji C, a raczej są zainteresowani rozszerzeniem właściwości poprawności, które mogą udowodnić. Z tego powodu nie ma znaczenia, czy obsługują C90, C99, C11, Pascal, Fortran, Algol, czy Plankalkül.
Jörg W Mittag
8
@martineau: Jestem ostrożny z tymi „frazami bozonów”, w których programiści nie zgadzają się ze sobą, twierdząc, że jest zły lub dobry, ponieważ będą siedzieć, jeśli tego nie zrobią. Powody muszą być bardziej podstawowe.
Mike Dunlavey
63

Dlaczego jest niebezpieczny?

  • gotosam w sobie nie powoduje niestabilności. Mimo około 100 000 gotos jądro Linuksa wciąż jest modelem stabilności.
  • gotosam w sobie nie powinien powodować luk w zabezpieczeniach. W językach, jednak niektóre zmieszanie go z try/ catchwyjątku bloki zarządzania może prowadzić do luk w sposób opisany w niniejszym zaleceniu CERT . Kompilatory głównego nurtu C ++ sygnalizują i zapobiegają takim błędom, ale niestety starsze lub bardziej egzotyczne kompilatory tego nie robią.
  • gotopowoduje nieczytelny i niemożliwy do utrzymania kod. Nazywa się to również kodem spaghetti , ponieważ podobnie jak na talerzu do spaghetti bardzo trudno jest podążać za kontrolą, gdy jest zbyt wiele goto.

Nawet jeśli uda ci się uniknąć kodu spaghetti i jeśli użyjesz tylko kilku gotów, nadal ułatwiają one takie błędy, jak i wyciekanie zasobów:

  • Kod wykorzystujący programowanie struktur z wyraźnymi zagnieżdżonymi blokami i pętlami lub przełącznikami jest łatwy do naśladowania; jego przepływ kontroli jest bardzo przewidywalny. Dlatego łatwiej jest zapewnić przestrzeganie niezmienników.
  • Za pomocą gotooświadczenia przerywasz ten prosty przepływ i przełamujesz oczekiwania. Na przykład możesz nie zauważyć, że nadal musisz zwolnić zasoby.
  • Wiele gotow różnych miejscach może wysłać cię do jednego celu goto. Więc nie jest oczywiste, aby wiedzieć na pewno, w jakim jesteś stanie, gdy dotrzesz do tego miejsca. Ryzyko dokonywania błędnych / bezpodstawnych założeń jest zatem dość duże.

Dodatkowe informacje i cytaty:

C zapewnia nieskończenie nadużywające gotooświadczenie i etykiety, do których można się rozgałęzić. Formalnie gotonigdy nie jest to konieczne, a w praktyce prawie zawsze łatwo jest napisać kod bez niego. (...)
Niemniej jednak zasugerujemy kilka sytuacji, w których goto może znaleźć miejsce. Najczęstszym zastosowaniem jest porzucenie przetwarzania w niektórych głęboko zagnieżdżonych strukturach, takich jak zerwanie dwóch pętli jednocześnie. (...)
Chociaż nie jesteśmy dogmatami w tej sprawie, wydaje się, że oświadczenia goto powinny być stosowane oszczędnie, jeśli w ogóle .

  • James Gosling i Henry McGilton napisali w swojej białej księdze z 1995 r. Dotyczącej środowiska Java :

    Nigdy więcej instrukcji Goto
    Java nie ma instrukcji goto. Badania pokazały, że goto jest (źle) używane częściej niż nie tylko „ponieważ tam jest”. Wyeliminowanie goto doprowadziło do uproszczenia języka (...) Badania około 100 000 linii kodu C wykazały, że około 90 procent instrukcji goto zostało wykorzystanych wyłącznie w celu uzyskania efektu przerwania zagnieżdżonych pętli. Jak wspomniano powyżej, wielopoziomowy podział i kontynuacja usuwają większość potrzeby instrukcji goto.

  • Bjarne Stroustrup definiuje goto w swoim glosariuszu w następujących zachęcających terminach:

    goto - niesławny goto. Przydatny przede wszystkim w generowanym maszynowo kodzie C ++.

Kiedy można go użyć?

Podobnie jak K&R, nie jestem dogmatyczny w kwestii gotos. Przyznaję, że są sytuacje, w których goto może ułatwić sobie życie.

Zazwyczaj w C goto pozwala na wielopoziomowe wyjście z pętli lub obsługę błędów wymagającą dotarcia do odpowiedniego punktu wyjścia, który uwalnia / odblokowuje wszystkie zasoby, które zostały przydzielone do tej pory (wielokrotna alokacja w sekwencji oznacza wiele etykiet). W tym artykule opisano różne zastosowania goto w jądrze systemu Linux.

Osobiście wolę tego unikać i za 10 lat C użyłem maksymalnie 10 gotów. Wolę używać zagnieżdżonych if, które moim zdaniem są bardziej czytelne. Gdyby to prowadziło do zbyt głębokiego zagnieżdżenia, wolałbym albo rozłożyć moją funkcję na mniejsze części, albo użyć wskaźnika logicznego w kaskadzie. Dzisiejsze kompilatory optymalizujące są wystarczająco sprytne, aby wygenerować prawie ten sam kod niż ten sam kod goto.

Zastosowanie goto w dużej mierze zależy od języka:

  • W C ++ prawidłowe użycie RAII powoduje, że kompilator automatycznie niszczy obiekty, które wykraczają poza zakres, tak że zasoby / blokada i tak zostaną wyczyszczone, i nie ma już potrzeby używania goto.

  • W Javie nie ma potrzeby goto (patrz autora cytat Javy powyżej i to doskonałe przepełnieniem stosu odpowiedź ): garbage collector że czyści bałagan break, continuei try/ catchobsługa wyjątków obejmują wszystkie sprawy, gdzie gotomogłyby być pomocne, ale w bezpieczniejszym i lepiej sposób. Popularność Javy dowodzi, że oświadczenia goto można uniknąć w nowoczesnym języku.

Powiększ słynną lukę w zabezpieczeniach SSL goto fail

Ważna informacja: z uwagi na zaciętą dyskusję w komentarzach chcę wyjaśnić, że nie udaję, że jedyną przyczyną tego błędu jest instrukcja goto. Nie udaję, że bez goto nie byłoby błędu. Chcę tylko pokazać, że goto może być związane z poważnym błędem.

Nie wiem, z iloma poważnymi błędami wiąże się gotohistoria programowania: szczegóły często nie są przekazywane. Był jednak znany błąd Apple SSL, który osłabił bezpieczeństwo iOS. Oświadczenie, które doprowadziło do tego błędu, było błędne goto.

Niektórzy twierdzą, że główną przyczyną błędu nie była sama instrukcja goto, ale niewłaściwe kopiowanie / wklejanie, wprowadzające w błąd wcięcie, brak nawiasów klamrowych wokół bloku warunkowego lub może nawyki pracy programisty. Nie mogę potwierdzić żadnego z nich: wszystkie te argumenty są prawdopodobnymi hipotezami i interpretacją. Nikt tak naprawdę nie wie. ( tymczasem hipoteza scalenia, która poszła nie tak, jak ktoś sugerował w komentarzach, wydaje się być bardzo dobrym kandydatem, biorąc pod uwagę inne niespójności wcięcia w tej samej funkcji ).

Jedynym obiektywnym faktem jest to, że duplikat gotodoprowadził do przedwczesnego wyjścia z funkcji. Patrząc na kod, jedyną inną instrukcją, która mogła wywołać ten sam efekt, byłby zwrot.

Błąd działa SSLEncodeSignedServerKeyExchange()w tym pliku :

    if ((err = ReadyHash(&SSLHashSHA1, &hashCtx)) != 0)
        goto fail;
    if ((err =...) !=0)
        goto fail;
    if ((err = SSLHashSHA1.update(&hashCtx, &signedParams)) != 0)
        goto fail;
        goto fail;  // <====OUCH: INDENTATION MISLEADS: THIS IS UNCONDITIONDAL!!
    if (...)
        goto fail;

    ... // Do some cryptographic operations here

fail:
    ... // Free resources to process error

Rzeczywiście, nawiasy klamrowe wokół bloku warunkowego mogłyby zapobiec
błędowi : doprowadziłoby to albo do błędu składniowego przy kompilacji (a zatem do korekty), albo do zbędnego nieszkodliwego goto. Nawiasem mówiąc, GCC 6 byłby w stanie wykryć te błędy dzięki opcjonalnemu ostrzeżeniu, aby wykryć niespójne wcięcie.

Ale przede wszystkim tych wszystkich gotów można było uniknąć dzięki bardziej uporządkowanemu kodowi. Więc goto jest przynajmniej pośrednią przyczyną tego błędu. Istnieją co najmniej dwa różne sposoby, aby tego uniknąć:

Podejście 1: jeśli klauzula lub zagnieżdżone ifs

Zamiast sekwencyjnego testowania wielu warunków pod kątem błędu i za każdym razem, gdy wysyłany jest do failetykiety w przypadku problemu, można było zdecydować się na wykonanie operacji kryptograficznych w stanie if, który zrobiłby to tylko, gdyby nie było złego warunku wstępnego:

    if ((err = ReadyHash(&SSLHashSHA1, &hashCtx)) == 0 &&
        (err = ...) == 0 ) &&
        (err = ReadyHash(&SSLHashSHA1, &hashCtx)) == 0) &&
        ...
        (err = ...) == 0 ) )
    {
         ... // Do some cryptographic operations here
    }
    ... // Free resources

Podejście 2: użyj akumulatora błędów

Podejście to opiera się na fakcie, że prawie wszystkie instrukcje tutaj wywołują jakąś funkcję w celu ustawienia errkodu błędu i wykonują resztę kodu tylko wtedy, gdy errwynosi 0 (tj. Funkcja wykonana bez błędu). Dobrą bezpieczną i czytelną alternatywą jest:

bool ok = true;
ok =  ok && (err = ReadyHash(&SSLHashSHA1, &hashCtx))) == 0;
ok =  ok && (err = NextFunction(...)) == 0;
...
ok =  ok && (err = ...) == 0;
... // Free resources

Tutaj nie ma jednego goto: nie ma ryzyka, aby szybko przejść do punktu wyjścia z awarii. A wizualnie łatwo byłoby zauważyć niedopasowaną linię lub zapomnianą ok &&.

Ta konstrukcja jest bardziej zwarta. Opiera się na fakcie, że w C druga część logicznego i ( &&) jest oceniana tylko wtedy, gdy pierwsza część jest prawdziwa. W rzeczywistości asembler wygenerowany przez kompilator optymalizacyjny jest prawie równoważny oryginalnemu kodowi z gotos: Optymalizator bardzo dobrze wykrywa łańcuch warunków i generuje kod, który przy pierwszej niezerowej wartości zwrotnej przeskakuje na koniec ( dowód online ).

Można nawet przewidzieć kontrolę spójności na końcu funkcji, która może podczas fazy testowania zidentyfikować niedopasowania między flagą ok a kodem błędu.

assert( (ok==false && err!=0) || (ok==true && err==0) );

Błędy takie jak ==0przypadkowo zastąpione !=0błędami lub logicznymi błędami złącza można łatwo wykryć podczas fazy debugowania.

Jak powiedziano: nie udaję, że alternatywne konstrukcje uniknęłyby jakiegokolwiek błędu. Chcę tylko powiedzieć, że mogły sprawić, że błąd będzie trudniejszy do wystąpienia.

Christophe
źródło
34
Nie, błąd polegający na niepowodzeniu Apple został spowodowany przez sprytnego programistę, który postanowił nie umieszczać nawiasów klamrowych po instrukcji if. :-) Więc kiedy pojawił się następny programista konserwacji (lub ten sam, ta sama różnica) i dodał kolejny wiersz kodu, który powinien był zostać wykonany tylko wtedy, gdy instrukcja if jest oceniana jako prawdziwa, instrukcja działała za każdym razem. Bam, błąd „goto fail”, który był poważnym błędem bezpieczeństwa. Ale zasadniczo nie miało to nic wspólnego z faktem, że użyto wyrażenia goto.
Craig
47
Błąd „goto fail” firmy Apple był bezpośrednio spowodowany powieleniem linii kodu. Szczególny efekt tego błędu był spowodowany tym, że wiersz ten był instrukcją bez gotonawiasów if. Ale jeśli sugerujesz, że unikanie gotouczyni twój kod odpornym na działanie losowo powielonych linii, mam dla ciebie złe wieści.
immibis
12
Używasz zachowania operatora logicznego skrótu, aby wykonać instrukcję poprzez efekt uboczny i twierdzić, że jest bardziej przejrzysta niż ifinstrukcja? Dobre czasy. :)
Craig
38
Gdyby zamiast „goto fail” pojawiło się zdanie „failed = true”, dokładnie tak samo by się stało.
gnasher729
15
Ten błąd został spowodowany przez niechlujnego programistę, który nie zwracał uwagi na to, co robił, i nie czytał własnych zmian w kodzie. Nic dodać nic ująć. Okej, może wrzucę też trochę niedopatrzeń testowych.
Wyścigi lekkości na orbicie
34

Słynny artykuł Dijkstry został napisany w czasie, gdy niektóre języki programowania były w stanie tworzyć podprogramy posiadające wiele punktów wejścia i wyjścia. Innymi słowy, możesz dosłownie wskoczyć na środek funkcji i wyskoczyć z dowolnego miejsca w funkcji, bez faktycznego wywoływania tej funkcji lub powrotu z niej w konwencjonalny sposób. To nadal dotyczy języka asemblera. Nikt nigdy nie twierdzi, że takie podejście jest lepsze od ustrukturyzowanej metody pisania oprogramowania, z której teraz korzystamy.

W większości współczesnych języków programowania funkcje są ściśle określone za pomocą jednego wejścia i jednego punktu wyjścia. Punkt wejścia to miejsce, w którym określasz parametry funkcji i wywołujesz ją, a punkt wyjścia to miejsce, w którym zwracasz wynikową wartość i kontynuujesz wykonywanie zgodnie z instrukcją po oryginalnym wywołaniu funkcji.

W ramach tej funkcji powinieneś być w stanie robić, co chcesz, w granicach rozsądku. Jeśli wstawienie goto lub dwóch w funkcji czyni je bardziej zrozumiałym lub poprawia prędkość, dlaczego nie? Sedno funkcji polega na zsekwencjonowaniu odrobiny jasno określonej funkcjonalności, abyś nie musiał myśleć o tym, jak działa ona wewnętrznie. Po napisaniu wystarczy go użyć.

I tak, możesz mieć wiele instrukcji return wewnątrz funkcji; zawsze jest jedno miejsce w odpowiedniej funkcji, z którego powracasz (w zasadzie tylna strona funkcji). To wcale nie jest to samo, co wyskakiwanie z funkcji z goto, zanim będzie ona miała szansę powrócić.

wprowadź opis zdjęcia tutaj

Więc tak naprawdę nie chodzi o używanie goto. Chodzi o unikanie ich nadużyć. Wszyscy zgadzają się, że możesz stworzyć strasznie skomplikowany program za pomocą gotos, ale możesz zrobić to samo, nadużywając funkcji (o wiele łatwiej jest nadużywać gotos).

Co jest warte, odkąd skończyłem z programów BASIC w stylu numeru linii do programowania strukturalnego przy użyciu języków Pascal i nawiasów klamrowych, nigdy nie musiałem używać goto. Kusiło mnie, aby użyć jednego z nich, aby zrobić wczesne wyjście z zagnieżdżonej pętli (w językach, które nie obsługują wielopoziomowego wczesnego wyjścia z pętli), ale zwykle mogę znaleźć inny sposób, który jest czystszy.

Robert Harvey
źródło
2
Komentarze nie są przeznaczone do rozszerzonej dyskusji; ta rozmowa została przeniesiona do czatu .
wałek klonowy
11

Do jakiego rodzaju błędów prowadzą instrukcje „goto”? Czy są jakieś historycznie znaczące przykłady?

gotoKiedy pisałem programy BASIC jako dziecko, używałem instrukcji jako prostego sposobu uzyskiwania pętli for i while (Commodore 64 BASIC nie miał pętli while i byłem zbyt niedojrzały, aby nauczyć się właściwej składni i użycia pętli for ). Mój kod był często trywialny, ale wszelkie błędy w pętli można było natychmiast przypisać mojemu użyciu goto.

Teraz używam głównie Pythona, języka programowania wysokiego poziomu, który stwierdził, że nie potrzebuje goto.

Kiedy Edsger Dijkstra ogłosił w 1968 r., Że „Goto uważa się za szkodliwe”, nie podał kilku przykładów, w których powiązane błędy można obwinić za goto, ale stwierdził, że nie jest gototo konieczne w przypadku języków wyższego poziomu i że należy tego unikać na korzyść tego, co rozważamy dzisiaj normalny przepływ sterowania: pętle i warunki warunkowe. Jego słowa:

Nieokiełznane użycie go ma natychmiastową konsekwencję, że strasznie trudno jest znaleźć znaczący zestaw współrzędnych, w których można opisać postęp procesu.
[...] Idź do zestawienia w obecnej formie jest po prostu zbyt prymitywne; to zbyt duże zaproszenie, aby zrobić bałagan w swoim programie.

Prawdopodobnie miał mnóstwo przykładów błędów za każdym razem, gdy debugował gotow nim kod . Ale jego artykuł był uogólnionym oświadczeniem o stanowisku popartym dowodem, który gotobył zbędny w przypadku języków wyższego poziomu. Uogólniony błąd polega na tym, że możesz nie mieć możliwości statycznej analizy kwestionowanego kodu.

Kod jest znacznie trudniejszy do analizy statycznej za pomocą gotoinstrukcji, szczególnie jeśli cofniesz się w przepływie kontrolnym (co kiedyś robiłem) lub do jakiejś niepowiązanej sekcji kodu. Kod może i został napisany w ten sposób. Zrobiono to, aby zoptymalizować bardzo ograniczone zasoby komputerowe, a tym samym architekturę maszyn.

Przykład apokryficzny

Był program blackjacka, który opiekun stwierdził, że jest dość elegancko zoptymalizowany, ale również niemożliwy do „naprawienia” ze względu na naturę kodu. Został zaprogramowany w kodzie maszynowym, który jest w dużej mierze zależny od gotos - więc myślę, że ta historia jest całkiem trafna. To najlepszy kanoniczny przykład, jaki mogę wymyślić.

Kontrprzykład

Jednak źródło C CPython (najczęstsza i referencyjna implementacja Pythona) używa gotoinstrukcji z doskonałym skutkiem. Służą do omijania nieistotnego przepływu sterowania wewnątrz funkcji, aby dotrzeć do końca funkcji, dzięki czemu funkcje są bardziej wydajne bez utraty czytelności. Jest to zgodne z jedną z idei programowania strukturalnego - posiadania jednego punktu wyjścia dla funkcji.

Dla przypomnienia, ten kontrprzykład jest dość łatwy do analizy statycznej.

Aaron Hall
źródło
5
„Historia Mela”, którą spotkałem, dotyczyła dziwnej architektury, w której (1) każda instrukcja języka maszynowego robi goto po swojej operacji, i (2) umieszczenie sekwencji instrukcji w kolejnych lokalizacjach pamięci było wyjątkowo nieefektywne. Program został napisany w ręcznie zoptymalizowanym zestawie, a nie w skompilowanym języku.
@MilesRout Myślę, że możesz mieć rację tutaj, ale strona Wikipedii na temat programowania strukturalnego mówi: „Najczęstszym odchyleniem, występującym w wielu językach, jest użycie instrukcji return do wcześniejszego wyjścia z podprogramu. Powoduje to wiele punktów wyjścia , zamiast pojedynczego punktu wyjścia wymaganego przez programowanie strukturalne. ” - Mówisz, że to nieprawda? Czy masz źródło do cytowania?
Aaron Hall,
@AaronHall To nie jest oryginalne znaczenie.
Miles Rout
11

Kiedy wigwam był obecną sztuką architektoniczną, jego budowniczowie mogli bez wątpienia udzielić ci praktycznych porad dotyczących budowy wigwamów, jak pozwolić dymowi na ucieczkę i tak dalej. Na szczęście dzisiejsi budowniczowie prawdopodobnie zapomnieli większość z tych rad.

Kiedy dyliżans był obecną sztuką transportową, jego kierowcy mogli bez wątpienia udzielić ci praktycznych porad dotyczących koni dyliżansów, jak bronić się przed rozbójnikami i tak dalej. Na szczęście dzisiejsi kierowcy również mogą zapomnieć o większości tych rad.

Kiedy karty perforowane były aktualną sztuką programowania, ich praktycy mogli również udzielić ci praktycznych porad dotyczących organizacji kart, sposobu numerowania wyciągów i tak dalej. Nie jestem pewien, czy ta rada jest dziś bardzo aktualna.

Czy jesteś wystarczająco stary, aby znać wyrażenie „numerować oświadczenia” ? Nie musisz tego wiedzieć, ale jeśli tego nie wiesz, to nie znasz historycznego kontekstu, w którym ostrzeżenie przeciwko gotobyło zasadniczo istotne.

Ostrzeżenie przed gotodzisiaj nie jest zbyt istotne. Dzięki podstawowemu szkoleniu w pętlach while / for i wywoływaniu funkcji nie będziesz nawet myśleć o wydawaniu gotozbyt często. Kiedy myślisz o tym, prawdopodobnie masz powód, więc śmiało.

Ale czy gotonie można nadużywać tego oświadczenia?

Odpowiedź: pewnie, może być nadużywane, ale jego nadużycie jest dość drobnym problemem w inżynierii oprogramowania w porównaniu do znacznie bardziej powszechnych błędów, takich jak używanie zmiennej, w której będzie służyć stała, lub takie jak programowanie typu „wklej i wklej” (w przeciwnym razie znany jako zaniedbanie refaktoryzacji). Wątpię, czy jesteś w niebezpieczeństwie. O ile nie używasz longjmplub nie przenosisz kontroli do odległego kodu, jeśli myślisz o użyciu gotolub chcesz po prostu wypróbować dla zabawy, śmiało. Wydobrzejesz.

Możesz zauważyć brak ostatnich opowiadań grozy, w których gotogra czarny charakter. Większość lub wszystkie te historie wydają się mieć 30 lub 40 lat. Stajesz na solidnym gruncie, jeśli uważasz te historie za w większości przestarzałe.

thb
źródło
1
Widzę, że przyciągnąłem przynajmniej jedną opinię. Tego można się spodziewać. Może przyjść więcej głosów negatywnych. Nie zamierzam jednak udzielać konwencjonalnej odpowiedzi, kiedy dekady doświadczenia w programowaniu nauczyły mnie, że w tym przypadku konwencjonalna odpowiedź jest błędna. W każdym razie to mój pogląd. Podałem swoje powody. Czytelnik może zdecydować.
thb
1
Twój post jest zbyt dobry, nie mogę przegłosować komentarzy.
Pieter B,
7

Aby dodać jedną rzecz do innych doskonałych odpowiedzi, za pomocą gotoinstrukcji może być trudno powiedzieć dokładnie, jak dostałeś się do dowolnego miejsca w programie. Możesz wiedzieć, że wystąpił wyjątek w określonym wierszu, ale jeśli gotow kodzie jest taki kod, nie ma sposobu, aby stwierdzić, które instrukcje wykonane w wyniku tego wyjątku powodują stan bez przeszukiwania całego programu. Nie ma stosu wywołań ani przepływu wizualnego. Możliwe, że istnieje instrukcja oddalona o 1000 linii, która wprawia cię w zły stan, wykonując a gotodo linii, która zgłosiła wyjątek.

qfwfq
źródło
1
Visual Basic 6 i VBA mają bolesną obsługę błędów, ale jeśli używasz On Error Goto errh, możesz Resumespróbować ponownie, Resume Nextpominąć linię obrażającą i Erldowiedzieć się, która to linia (jeśli używasz numerów linii).
Cees Timmerman
@CeesTimmerman Z Visual Basic niewiele zrobiłem, nie wiedziałem o tym. Dodam komentarz na ten temat.
qfwfq
2
@CeesTimmerman: Semantyka VB6 „wznowienia” jest okropna, jeśli wystąpi błąd w podprogramie, który nie ma własnego bloku błędu. Resumeponownie uruchomi tę funkcję od początku i Resume Nextpominie wszystko w funkcji, która jeszcze tego nie zrobiła.
supercat
2
@ superupat Jak powiedziałem, to bolesne. Jeśli potrzebujesz znać dokładną linię, powinieneś je wszystkie numerować lub tymczasowo wyłączyć obsługę błędów za pomocą On Error Goto 0i ręcznie debugować. Resumejest przeznaczony do użycia po sprawdzeniu Err.Numberi dokonaniu niezbędnych korekt.
Cees Timmerman
1
Powinienem zauważyć, że w nowoczesnym języku gotodziała tylko z oznaczonymi liniami, które wydają się być zastąpione przez oznaczone pętle .
Cees Timmerman
7

Goto jest trudniejszy do zrozumienia dla ludzi niż inne formy kontroli przepływu.

Programowanie poprawnego kodu jest trudne. Pisanie poprawnych programów jest trudne, ustalenie, czy programy są poprawne, jest trudne, udowodnienie, że programy są poprawne, jest trudne.

Sprawienie, by kod niejasno robił, co chcesz, jest łatwe w porównaniu ze wszystkim innym na temat programowania.

goto rozwiązuje niektóre programy, uzyskując kod do robienia tego, co chcesz. Nie ułatwia to sprawdzania poprawności, a często robią to alternatywy.

Istnieją style programowania, w których goto jest właściwym rozwiązaniem. Istnieją nawet style programowania, w których jego zły bliźniak, comefrom, jest właściwym rozwiązaniem. W obu tych przypadkach należy zachować szczególną ostrożność, aby upewnić się, że używasz go w zrozumiały sposób, oraz wiele ręcznych kontroli, czy nie robisz czegoś trudnego w celu zapewnienia poprawności.

Na przykład istnieje funkcja niektórych języków zwana coroutines. Coroutines to nici bezgwintowe; stan wykonania bez wątku, na którym można go uruchomić. Możesz poprosić ich o wykonanie, a oni mogą uruchomić część siebie, a następnie zawiesić się, przekazując kontrolę przepływu zwrotnego.

„Płytkie” coroutine w językach bez obsługi coroutine (jak C ++ przed C ++ 20 i C) są możliwe przy użyciu kombinacji gotos i ręcznego zarządzania stanem. „Głębokie” coroutines można wykonać za pomocą funkcji setjmpi longjmpC.

Są przypadki, w których coroutines są tak przydatne, że warto je pisać ręcznie i ostrożnie.

W przypadku C ++ są one na tyle przydatne, że rozszerzają język, aby je obsługiwać. W gotos i instrukcja zarządzania stan jest ukryty za zerowy koszt warstwy abstrakcji, co pozwala programistom pisać je bez trudności z konieczności udowodnienia ich bałagan Goto, void**s, ręcznej budowy / destrukcji państwa, etc jest poprawna.

Goto ukrywa się za abstrakcją wyższego poziomu, taką jak whilelub forlub iflub switch. Te abstrakcje wyższego poziomu łatwiej jest udowodnić i sprawdzić.

Jeśli brakuje jakiegoś języka (jak w niektórych współczesnych językach brakuje coroutine), albo kulejąc wraz ze wzorem, który nie pasuje do problemu, albo używając goto, staje się twoją alternatywą.

Łatwo jest sprawić, by komputer wykonywał niejasno to, co chcesz w typowych przypadkach . Pisanie niezawodnie solidnego kodu jest trudne . Goto pomaga pierwszemu znacznie bardziej niż drugiemu; stąd „został uznany za szkodliwy”, ponieważ jest to znak powierzchownie „działającego” kodu z głębokimi i trudnymi do wyśledzenia błędami. Przy wystarczającym wysiłku możesz nadal tworzyć niezawodnie niezawodny kod z „goto”, więc trzymanie się reguły absolutnej jest błędne; ale z reguły jest dobry.

Jak
źródło
1
longjmpjest dozwolone tylko w C dla odwijania stosu z powrotem do setjmpfunkcji nadrzędnej. (Jak przełamywanie wielu poziomów zagnieżdżania, ale dla wywołań funkcji zamiast pętli). longjjmpdo kontekstu z funkcji setjmpwykonanej w funkcji, która została zwrócona, nie jest legalna (i podejrzewam, że w większości przypadków nie będzie działać). Nie znam wspólnych procedur, ale z twojego opisu wspólnej procedury jako podobnej do wątku przestrzeni użytkownika, myślę, że potrzebowałby własnego stosu, a także kontekstu zapisanych rejestrów i longjmpdaje tylko rejestr - oszczędzanie, a nie osobny stos.
Peter Cordes
1
Och, powinienem był googledować : fanf.livejournal.com/105413.html opisuje tworzenie miejsca na stosy dla coroutine. (Co, jak podejrzewałam, jest konieczne oprócz użycia setjmp / longjmp).
Peter Cordes
2
@PeterCordes: Implementacje nie są wymagane w celu zapewnienia jakichkolwiek środków, za pomocą których kod mógłby stworzyć parę jmp_buff, która nadaje się do użycia jako coroutine. Niektóre implementacje określają środki, za pomocą których kod może je utworzyć, mimo że Standard nie wymaga od nich takiej możliwości. Nie opisałbym kodu, który opiera się na takich technikach jak „standardowy C”, ponieważ w wielu przypadkach jmp_buff będzie nieprzezroczystą strukturą, która nie gwarantuje, że będzie zachowywać się spójnie między różnymi kompilatorami.
supercat
6

Spójrz na ten kod z http://www-personal.umich.edu/~axe/research/Software/CC/CC2/TourExec1.1.f.html, który faktycznie był częścią dużej symulacji Dylematu Więźnia. Jeśli widziałeś stary kod FORTRAN lub BASIC, zdasz sobie sprawę, że nie jest to takie niezwykłe.

C  Not nice rules in second round of tour (cut and pasted 7/15/93)
   FUNCTION K75R(J,M,K,L,R,JA)
C  BY P D HARRINGTON
C  TYPED BY JM 3/20/79
   DIMENSION HIST(4,2),ROW(4),COL(2),ID(2)
   K75R=JA       ! Added 7/32/93 to report own old value
   IF (M .EQ. 2) GOTO 25
   IF (M .GT. 1) GOTO 10
   DO 5 IA = 1,4
     DO 5 IB = 1,2
5  HIST(IA,IB) = 0

   IBURN = 0
   ID(1) = 0
   ID(2) = 0
   IDEF = 0
   ITWIN = 0
   ISTRNG = 0
   ICOOP = 0
   ITRY = 0
   IRDCHK = 0
   IRAND = 0
   IPARTY = 1
   IND = 0
   MY = 0
   INDEF = 5
   IOPP = 0
   PROB = .2
   K75R = 0
   RETURN

10 IF (IRAND .EQ. 1) GOTO 70
   IOPP = IOPP + J
   HIST(IND,J+1) = HIST(IND,J+1) + 1
   IF (M .EQ. 15 .OR. MOD(M,15) .NE. 0 .OR. IRAND .EQ. 2) GOTO 25
   IF (HIST(1,1) / (M - 2) .GE. .8) GOTO 25
   IF (IOPP * 4 .LT. M - 2 .OR. IOPP * 4 .GT. 3 * M - 6) GOTO 25
   DO 12 IA = 1,4
12 ROW(IA) = HIST(IA,1) + HIST(IA,2)

   DO 14 IB = 1,2
     SUM = .0
     DO 13 IA = 1,4
13   SUM = SUM + HIST(IA,IB)
14 COL(IB) = SUM

   SUM = .0
   DO 16 IA = 1,4
     DO 16 IB = 1,2
       EX = ROW(IA) * COL(IB) / (M - 2)
       IF (EX .LE. 1.) GOTO 16
       SUM = SUM + ((HIST(IA,IB) - EX) ** 2) / EX
16 CONTINUE

   IF (SUM .GT. 3) GOTO 25
   IRAND = 1
   K75R = 1
   RETURN

25 IF (ITRY .EQ. 1 .AND. J .EQ. 1) IBURN = 1
   IF (M .LE. 37 .AND. J .EQ. 0) ITWIN = ITWIN + 1
   IF (M .EQ. 38 .AND. J .EQ. 1) ITWIN = ITWIN + 1
   IF (M .GE. 39 .AND. ITWIN .EQ. 37 .AND. J .EQ. 1) ITWIN = 0
   IF (ITWIN .EQ. 37) GOTO 80
   IDEF = IDEF * J + J
   IF (IDEF .GE. 20) GOTO 90
   IPARTY = 3 - IPARTY
   ID(IPARTY) = ID(IPARTY) * J + J
   IF (ID(IPARTY) .GE. INDEF) GOTO 78
   IF (ICOOP .GE. 1) GOTO 80
   IF (M .LT. 37 .OR. IBURN .EQ. 1) GOTO 34
   IF (M .EQ. 37) GOTO 32
   IF (R .GT. PROB) GOTO 34
32 ITRY = 2
   ICOOP = 2
   PROB = PROB + .05
   GOTO 92

34 IF (J .EQ. 0) GOTO 80
   GOTO 90

70 IRDCHK = IRDCHK + J * 4 - 3
   IF (IRDCHK .GE. 11) GOTO 75
   K75R = 1
   RETURN

75 IRAND = 2
   ICOOP = 2
   K75R = 0
   RETURN

78 ID(IPARTY) = 0
   ISTRNG = ISTRNG + 1
   IF (ISTRNG .EQ. 8) INDEF = 3
80 K75R = 0
   ITRY = ITRY - 1
   ICOOP = ICOOP - 1
   GOTO 95

90 ID(IPARTY) = ID(IPARTY) + 1
92 K75R = 1
95 IND = 2 * MY + J + 1
   MY = K75R
   RETURN
   END

Jest tutaj wiele problemów, które znacznie wykraczają poza oświadczenie GOTO; Szczerze uważam, że oświadczenie GOTO było trochę kozłem ofiarnym. Ale przepływ sterowania nie jest tutaj absolutnie jasny, a kod jest mieszany razem w taki sposób, że bardzo niejasne jest to, co się dzieje. Nawet bez dodawania komentarzy lub używania lepszych nazw zmiennych, zmiana na strukturę bloków bez GOTO znacznie ułatwiłaby czytanie i śledzenie.

prosfilaes
źródło
4
tak prawda :-) Warto wspomnieć: starsze FORTAN i BASIC nie miały struktur blokowych, więc jeśli klauzula THEN nie mogła utrzymać się w jednej linii, GOTO było jedyną alternatywą.
Christophe
Bardzo pomogłyby etykiety tekstowe zamiast liczb lub przynajmniej komentarze do linii numerowanych.
Cees Timmerman
Wracając do moich dni FORTRAN-IV: ograniczyłem używanie GOTO do implementowania bloków IF / ELSE IF / ELSE, WHILE loop oraz BREAK i NEXT w loopach. Ktoś pokazał mi zło kodu spaghetti i dlaczego powinieneś go ustrukturyzować, aby tego uniknąć, nawet jeśli musisz zaimplementować swoje struktury bloków za pomocą GOTO. Później zacząłem używać preprocesora (RATFOR, IIRC). Goto nie jest wewnętrznie złe, to po prostu zbyt potężny konstrukt niskiego poziomu, który mapuje się na instrukcję gałęzi asemblera. Jeśli musisz go używać, ponieważ Twój język jest niewystarczający, użyj go do zbudowania lub rozszerzenia odpowiednich struktur bloków.
nigel222,
@CeesTimmerman: To jest kod FORTRAN IV odmiany ogrodowej. Etykiety tekstowe nie były obsługiwane w FORTRAN IV. Nie wiem, czy obsługują je nowsze wersje języka. Komentarze w tym samym wierszu co kod nie były obsługiwane w standardowym FORTRAN IV, chociaż mogły istnieć rozszerzenia specyficzne dla dostawców, aby je obsługiwać. Nie wiem, co mówi „obecny” standard FORTRAN: dawno temu porzuciłem FORTRAN i nie tęsknię. (Najnowszy kod FORTRAN, który widziałem, mocno przypomina PASCAL z wczesnych lat 70.)
John R. Strohm,
1
@CeesTimmerman: Rozszerzenie specyficzne dla dostawcy. Kod ogólnie jest NAPRAWDĘ nieśmiały w komentarzach. Istnieje również konwencja, która stała się powszechna w niektórych miejscach, polegająca na umieszczaniu numerów wierszy tylko na instrukcjach CONTINUE i FORMAT, aby ułatwić późniejsze dodawanie wierszy. Ten kod nie jest zgodny z tą konwencją.
John R. Strohm,
0

Jedną z zasad programowalnego programowania jest hermetyzacja . Chodzi o to, że łączysz się z modułem / procedurą / podprogramem / komponentem / obiektem za pomocą zdefiniowanego interfejsu i tylko tego interfejsu, a wyniki będą przewidywalne (zakładając, że urządzenie zostało skutecznie przetestowane).

W ramach jednej jednostki kodu obowiązuje ta sama zasada. Jeśli konsekwentnie stosujesz zasady programowania strukturalnego lub programowania obiektowego, nie będziesz:

  1. utwórz nieoczekiwane ścieżki za pomocą kodu
  2. przybywają w sekcjach kodu o niezdefiniowanych lub niedopuszczalnych wartościach zmiennych
  3. wyjdź ze ścieżki kodu z niezdefiniowanymi lub niedopuszczalnymi wartościami zmiennych
  4. nie udało się wypełnić jednostki transakcyjnej
  5. zostaw w pamięci fragmenty kodu lub danych, które są faktycznie martwe, ale nie kwalifikują się do czyszczenia pamięci i ponownego przydzielenia, ponieważ nie zasygnalizowałeś ich zwolnienia za pomocą jawnej konstrukcji, która wywołuje ich zwolnienie, gdy ścieżka kontroli kodu wychodzi z jednostki

Niektóre z bardziej powszechnych objawów tych błędów przetwarzania obejmują wycieki pamięci, gromadzenie pamięci, przepełnienie wskaźnika, awarie, niekompletne rekordy danych, wyjątki dodawania / chg / usuwania rekordów, błędy stron pamięci i tak dalej.

Obserwowane przez użytkownika przejawy tych problemów obejmują blokowanie interfejsu użytkownika, stopniowe zmniejszanie wydajności, niekompletne zapisy danych, niemożność inicjowania lub wykonywania transakcji, uszkodzone dane, przerwy w sieci, przerwy w dostawie prądu, awarie systemów wbudowanych (z powodu utraty kontroli nad pociskami rakietowymi) utrata zdolności śledzenia i kontroli w systemach kontroli ruchu lotniczego), awarie czasomierzy itp. Katalog jest bardzo obszerny.

Jaxter
źródło
3
Odpowiedziałeś, nie wspominając gotoani razu. :)
Robert Harvey
0

goto jest niebezpieczne, ponieważ zwykle stosuje się go tam, gdzie nie jest potrzebny. Używanie czegokolwiek, co nie jest potrzebne, jest niebezpieczne, ale przede wszystkim musisz . Jeśli google znajdziesz wokół wielu błędów spowodowanych przez goto, nie jest to sam w sobie powód, aby go nie używać (błędy zawsze występują, gdy używasz funkcji językowych, ponieważ jest to nieodłączne w programowaniu), ale niektóre z nich są wyraźnie wysoce powiązane do goto użytkowania.


Powody używania / nieużywania goto :

  • Jeśli potrzebujesz pętli, musisz użyć whilelub for.

  • Jeśli potrzebujesz wykonać skok warunkowy, użyj if/then/else

  • Jeśli potrzebujesz procedury, wywołaj funkcję / metodę.

  • Jeśli chcesz wyjść z funkcji, po prostu return.

Mogę liczyć na palce w miejscach, w których widziałem gotoużywane i używane prawidłowo.

  • CPython
  • libKTX
  • prawdopodobnie jeszcze kilka

W libKTX jest funkcja, która ma następujący kod

if(something)
    goto cleanup;

if(bla)
    goto cleanup;

cleanup:
    delete [] array;

Teraz w tym miejscu gotojest przydatne, ponieważ językiem jest C:

  • jesteśmy w funkcji
  • nie możemy napisać funkcji czyszczenia (ponieważ wchodzimy w inny zakres, a udostępnienie stanu funkcji wywołującej jest większym obciążeniem)

Ten przypadek użycia jest przydatny, ponieważ w C nie mamy klas, więc najprostszym sposobem na oczyszczenie jest użycie goto.

Gdybyśmy mieli ten sam kod w C ++, nie ma już potrzeby używania goto:

class MyClass{
    public:

    void Function(){
        if(something)
            return Cleanup(); // invalid syntax in C#, but valid in C++
        if(bla)
            return Cleanup(); // invalid syntax in C#, but valid in C++
    }

    // access same members, no need to pass state (compiler do it for us).
    void Cleanup(){

    }



}

Do jakiego rodzaju błędów może to prowadzić? Byle co. Nieskończone pętle, niewłaściwa kolejność wykonywania, śruba stosu ..

Udokumentowana sprawa to luka w zabezpieczeniach SSL, która pozwoliła na ataki typu Man in the środkowego spowodowane niewłaściwym użyciem goto: oto artykuł

To błąd w literówce, ale przez jakiś czas był niezauważany, jeśli kod byłby skonstruowany w inny sposób, poprawnie przetestowany taki błąd nie mógłby być możliwy.

Twórca gier
źródło
2
Jedna odpowiedź zawiera wiele komentarzy, które bardzo wyraźnie wyjaśniają, że użycie „goto” było całkowicie nieistotne dla błędu SSL - błąd został spowodowany przez nieostrożne powielenie jednego wiersza kodu, więc raz został wykonany warunkowo, a raz bezwarunkowo.
gnasher729
Tak, czekam na lepszy historyczny przykład błędu goto, zanim zaakceptuję. Jak zostało powiedziane; To naprawdę nie miałoby znaczenia, co było w tej linii kodu; Brak nawiasów klamrowych wykonałby go i spowodowałby błąd niezależnie od tego. Jestem ciekawy; co to jest „śruba stosowa”?
Akiva
1
Przykład z kodu, nad którym wcześniej pracowałem w PL / 1 CICS. Program umieścił ekran składający się z map jedno liniowych. Kiedy ekran został zwrócony, znalazł szereg wysłanych map. Wykorzystał te elementy, aby znaleźć indeks w jednej tablicy, a następnie użył tego indeksu do indeksu zmiennych tablicowych, aby wykonać GOTO, aby mógł przetworzyć tę indywidualną mapę. Jeśli tablica wysłanych map była niepoprawna (lub została uszkodzona), może spróbować wykonać GOTO na niewłaściwej etykiecie lub gorzej, ponieważ uzyskiwał dostęp do elementu po końcu tablicy zmiennych etykiet. Przepisano, aby używać składni typu sprawy.
Kickstart,