Przykłady dobrych wyników w C lub C ++ [zamknięte]


79

W tym wątku przyjrzymy się przykładom dobrych zastosowań gotow C i C ++. Inspiruje się odpowiedzią, na którą ludzie głosowali, ponieważ myśleli, że żartuję.

Podsumowanie (zmieniono etykietę z oryginalnej, aby intencja była jeszcze jaśniejsza):

infinite_loop:

    // code goes here

goto infinite_loop;

Dlaczego jest lepszy niż alternatywy:

  • To jest specyficzne. gotojest konstrukcją języka, która powoduje bezwarunkową gałąź. Alternatywy zależą od użycia struktur obsługujących gałęzie warunkowe, ze zdegenerowanym, zawsze prawdziwym warunkiem.
  • Etykieta dokumentuje zamiar bez dodatkowych komentarzy.
  • Czytelnik nie musi skanować kodu interweniującego w poszukiwaniu wczesnych break(chociaż haker bez zasad nadal może przeprowadzić symulację continuez wczesnym goto).

Zasady:

  • Udawaj, że gotofobowie nie wygrali. Rozumie się, że powyższego nie można używać w prawdziwym kodzie, ponieważ jest to sprzeczne z ustalonym idiomem.
  • Załóżmy, że wszyscy słyszeliśmy o „Goto uważane za szkodliwe” i wiemy, że goto można wykorzystać do napisania kodu spaghetti.
  • Jeśli nie zgadzasz się z przykładem, skrytykuj go wyłącznie pod względem technicznym („Ponieważ ludzie nie lubią goto” nie jest technicznym powodem).

Zobaczmy, czy możemy mówić o tym jak dorośli.

Edytować

To pytanie wydaje się zakończone. To wygenerowało kilka wysokiej jakości odpowiedzi. Dziękuję wszystkim, zwłaszcza tym, którzy poważnie potraktowali mój przykład małej pętli. Większość sceptyków była zaniepokojona brakiem zakresu blokowego. Jak @quinmars wskazał w komentarzu, zawsze możesz założyć klamry wokół ciała pętli. Zwracam uwagę na to mimochodem for(;;)i while(true)nie dostarczam też szelek za darmo (a ich pominięcie może spowodować irytujące błędy). W każdym razie nie będę marnować więcej mocy twojego mózgu na ten drobiazg - mogę żyć z nieszkodliwym i idiomatycznym for(;;)i while(true)(równie dobrze, jeśli chcę zachować swoją pracę).

Biorąc pod uwagę inne odpowiedzi, widzę, że wielu ludzi postrzega to gotojako coś, co zawsze trzeba przepisać w inny sposób. Oczywiście możesz uniknąć a goto, wprowadzając pętlę, dodatkową flagę, stos zagnieżdżonych ifs lub cokolwiek innego, ale dlaczego nie zastanowić się, czy gotojest to być może najlepsze narzędzie do tego zadania? Innymi słowy, ile brzydoty są gotowi znieść ludzie, aby uniknąć używania wbudowanej funkcji języka zgodnie z jej przeznaczeniem? Uważam, że nawet dodanie flagi to zbyt wysoka cena do zapłacenia. Lubię, gdy moje zmienne reprezentują rzeczy w domenach problemów lub rozwiązań. „Tylko po to, by uniknąć goto” nie wystarcza.

Przyjmę pierwszą odpowiedź, która dała wzorzec C do rozgałęzienia do bloku porządkującego. IMO, to jest najsilniejszym argumentem za jedną gotoze wszystkich opublikowanych odpowiedzi, z pewnością jeśli mierzysz to przez wykrzywienia, przez które musi przejść hejter, aby tego uniknąć.


11
Nie rozumiem, dlaczego gotofobowie nie nakazują po prostu „#define goto report_to_your_supervisor_for_re_education_through_labour” na górze dołączanego pliku projektu. Jeśli zawsze jest źle, zrób to niemożliwe. W przeciwnym razie czasami jest dobrze ...
Steve Jessop,

14
„nie można używać w prawdziwym kodzie”? Używam go w "prawdziwym" kodzie za każdym razem, gdy jest to najlepsze narzędzie do pracy. „Established idiom” to fajny eufemizm dla „ślepego dogmatyzmu”.
Dan Molding

7
Zgadzam się, że „goto” może być przydatne (poniżej są świetne przykłady), ale nie zgadzam się z Twoim konkretnym przykładem. Linia z napisem „goto infinite_loop” brzmi tak, jakby oznaczała „przejdź do części kodu, w której zaczniemy na zawsze zapętlić”, na przykład „initialize (); set_things_up (); goto infinite_loop;” kiedy tak naprawdę chcesz przekazać, to „rozpocznij następną iterację pętli, w której już jesteśmy”, co jest zupełnie inne. Jeśli próbujesz zapętlić, a Twój język ma konstrukcje zaprojektowane specjalnie do zapętlania, użyj ich dla przejrzystości. while (true) {foo ()} jest dość jednoznaczne.
Josh

6
Jedną z głównych wad twojego przykładu jest to, że nie jest jasne, które inne miejsca w kodzie mogą zdecydować o przeskoczeniu do tej etykiety. „Normalna” nieskończona pętla ( for (;;)) nie ma zaskakujących punktów wejścia.
jalf

3
@ György: tak, ale to nie jest punkt wejścia.
jalf

Odpowiedzi:


87

Oto jedna sztuczka, której używają ludzie. Jednak nigdy nie widziałem tego na wolności. Dotyczy to tylko C, ponieważ C ++ ma RAII, aby robić to bardziej idiomatycznie.

void foo()
{
    if (!doA())
        goto exit;
    if (!doB())
        goto cleanupA;
    if (!doC())
        goto cleanupB;

    /* everything has succeeded */
    return;

cleanupB:
    undoB();
cleanupA:
    undoA();
exit:
    return;
}

2
Co w tym złego? (pomijając fakt, że komentarze nie rozumieją nowych linii) void foo () {if (doA ()) {if (doB ()) {if (doC ()) {/ * wszystko się udało * / return; } undoB (); } undoA (); } powrót; }
Artelius,

6
Dodatkowe bloki powodują niepotrzebne wcięcia, które są trudne do odczytania. Nie działa również, jeśli jeden z warunków znajduje się wewnątrz pętli.
Adam Rosenfield

26
Możesz zobaczyć dużo tego rodzaju kodu w większości niskopoziomowych rzeczy w Uniksie (na przykład w jądrze Linuksa). W C to najlepszy idiom do odzyskiwania błędów IMHO.
David Cournapeau

8
Jak wspomniał Cournape, jądro Linuksa używa tego stylu przez cały czas .
CesarB

8
Nie tylko jądro Linuksa przyjrzyj się przykładowym sterownikom Windows firmy Microsoft, a znajdziesz ten sam wzorzec. Ogólnie jest to sposób obsługi wyjątków w C i bardzo przydatny :). Zwykle wolę tylko 1 etykietę, aw kilku przypadkach 2. 3 można uniknąć w 99% przypadków.
Ilya

85

Klasyczna potrzeba GOTO w C jest następująca

for ...
  for ...
    if(breakout_condition) 
      goto final;

final:

Nie ma prostego sposobu na wyrwanie się z zagnieżdżonych pętli bez goto.


49
Najczęściej, gdy pojawia się taka potrzeba, mogę zamiast tego użyć zwrotu. Ale musisz pisać małe funkcje, aby działało w ten sposób.
Darius Bacon

7
Zdecydowanie zgadzam się z Dariusem - przerób to na funkcję i zamiast tego wróć!
metao

9
@metao: Bjarne Stroustrup nie zgadza się. W jego książce o języku programowania C ++ jest to dokładnie podany przykład „dobrego wykorzystania” goto.
paercebal

19
@Adam Rosenfield: Cały problem z goto, na który zwrócił uwagę Dijkstra, polega na tym, że programowanie strukturalne oferuje bardziej zrozumiałe konstrukcje. Uczynienie kodu trudniejszym do odczytania, aby uniknąć goto, dowodzi, że autor nie zrozumiał tego eseju ...
Steve Jessop

5
@Steve Jessop: Nie tylko ludzie źle zrozumieli esej, ale większość kultystów „nie idź do” nie rozumie kontekstu historycznego, w którym został napisany.
TYLKO MOJA poprawna OPINIA

32

Oto mój niemądry przykład (ze Stevens APITUE) dla wywołań systemowych Uniksa, które mogą być przerywane przez sygnał.

restart:
    if (system_call() == -1) {
        if (errno == EINTR) goto restart;

        // handle real errors
    }

Alternatywą jest zdegenerowana pętla. Ta wersja brzmi jak w języku angielskim „jeśli wywołanie systemowe zostało przerwane przez sygnał, uruchom je ponownie”.


3
Ten sposób jest używany, na przykład program planujący linux ma takie goto, ale powiedziałbym, że jest bardzo niewiele przypadków, w których goto wstecz jest akceptowalne i ogólnie należy ich unikać.
Ilya

8
@Amarghosh, continuejest tylko gotow masce.
jball

2
@jball ... hmm .. ze względu na kłótnię, tak; możesz stworzyć dobry czytelny kod za pomocą kodu goto i spaghetti za pomocą kontynuacji. Ostatecznie zależy to od osoby, która napisze kod. Chodzi o to, że łatwo jest się zgubić z goto niż z continue. A nowicjusze zwykle używają pierwszego młotka do każdego problemu.
Amarghosh

2
@Amarghosh. Twój „ulepszony” kod nie jest nawet odpowiednikiem oryginału - w przypadku sukcesu jest zapętlony na zawsze.
fizzer

3
@fizzer oopsie ... potrzeba dwóch sekund else break(po jednym na każde ifs), aby było równoważne .. co mogę powiedzieć ... gotolub nie, twój kod jest tak dobry jak ty :(
Amarghosh

14

Jeśli urządzenie Duffa nie potrzebuje goto, ty też nie powinieneś! ;)

void dsend(int count) {
    int n;
    if (!count) return;
    n = (count + 7) / 8;
    switch (count % 8) {
      case 0: do { puts("case 0");
      case 7:      puts("case 7");
      case 6:      puts("case 6");
      case 5:      puts("case 5");
      case 4:      puts("case 4");
      case 3:      puts("case 3");
      case 2:      puts("case 2");
      case 1:      puts("case 1");
                 } while (--n > 0);
    }
}

powyższy kod z wpisu w Wikipedii .


2
Chodźcie, jeśli macie zamiar zagłosować, krótki komentarz, dlaczego byłoby wspaniale. :)
Mitch Wheat

10
Jest to przykład błędu logicznego znanego również jako „odwołanie się do władzy”. Sprawdź to na Wikipedii.
Marcus Griep

11
humor jest tu często niedoceniany ...
Steven A. Lowe

8
czy urządzenie Duffa robi piwo?
Steven A. Lowe

6
ten może zrobić 8 na raz ;-)
Jasper Bekkers

14

Knuth napisał artykuł "Programowanie strukturalne z instrukcjami GOTO", możesz go pobrać np . Stąd . Znajdziesz tam wiele przykładów.


Ten artykuł wygląda głęboko. Chociaż to przeczytam. Dzięki
fizzer

2
Ten artykuł jest tak przestarzały, że nawet nie jest zabawny. Założenia Knutha po prostu nie są już aktualne.
Konrad Rudolph

3
Jakie założenia? Jego przykłady są dziś tak samo prawdziwe dla języka proceduralnego jak C (podaje je w jakimś pseudokodzie), jak były w tamtym czasie.
zvrba

8
jestem zawiedziony, że nie napisałeś: Możesz GOTO „tutaj”, żeby to dostać, gra słów byłaby dla mnie nie do zniesienia!
RhysW,

12

Bardzo częste.

do_stuff(thingy) {
    lock(thingy);

    foo;
    if (foo failed) {
        status = -EFOO;
        goto OUT;
    }

    bar;
    if (bar failed) {
        status = -EBAR;
        goto OUT;
    }

    do_stuff_to(thingy);

OUT:
    unlock(thingy);
    return status;
}

Jedynym przypadkiem, którego kiedykolwiek używam, gotojest skakanie do przodu, zwykle z bloków i nigdy do bloków. Pozwala to uniknąć nadużyć do{}while(0)i innych konstrukcji, które zwiększają zagnieżdżanie, zachowując jednocześnie czytelny, ustrukturyzowany kod.


5
Myślę, że jest to typowy sposób obsługi błędów w C. Nie widzę ładnie wymienionego, lepiej czytelnego w inny sposób. Pozdrawiam
Friedrich

Dlaczego nie ... do_stuff(thingy) { RAIILockerObject(thingy); ..... /* -->*/ } /* <---*/ :)
Петър Петров

1
@ ПетърПетров To wzorzec w C ++, który nie ma odpowiednika w C.
ephemient Kwietnia

Lub w jeszcze bardziej uporządkowanym języku niż C ++, try { lock();..code.. } finally { unlock(); }ale tak, C nie ma odpowiednika.
Blindy

12

Ogólnie nie mam nic przeciwko gotosom, ale przychodzi mi do głowy kilka powodów, dla których nie chciałbyś ich używać w pętli, o której wspomniałeś:

  • Nie ogranicza zakresu, dlatego wszelkie zmienne tymczasowe, których używasz wewnątrz, zostaną zwolnione dopiero później.
  • Nie ogranicza zakresu, więc może prowadzić do błędów.
  • Nie ogranicza zakresu, dlatego nie można ponownie użyć tych samych nazw zmiennych później w przyszłym kodzie w tym samym zakresie.
  • Nie ogranicza zakresu, więc masz możliwość pominięcia deklaracji zmiennej.
  • Ludzie nie są do tego przyzwyczajeni i utrudni to odczytanie kodu.
  • Zagnieżdżone pętle tego typu mogą prowadzić do kodu spaghetti, pętle normalne nie prowadzą do kodu spaghetti.

1
jest inf_loop: {/ * treść pętli * /} goto inf_loop; lepszy? :)
quinmars

2
Punkty 1-4 są wątpliwe w tym przykładzie nawet bez nawiasów klamrowych. 1 To nieskończona pętla. 2 Zbyt niejasne, aby się odnieść. 3 Próba ukrycia zmiennej o tej samej nazwie w obejmującym zakresie jest kiepską praktyką. 4. Wstecz goto nie może pominąć deklaracji.
fizzer

1-4 tak jak w przypadku wszystkich nieskończonych pętli, zazwyczaj w środku występuje stan przerwania. Więc mają zastosowanie. Re a bakward goto nie może pominąć deklaracji ... nie wszystkie goto są do tyłu ...
Brian R. Bondy,

7

Dobrym miejscem na użycie goto jest procedura, która może zostać przerwana w kilku punktach, z których każdy wymaga różnych poziomów czyszczenia. Gotofobowie zawsze mogą zastąpić gotos kodem strukturalnym i serią testów, ale myślę, że jest to prostsze, ponieważ eliminuje nadmierne wcięcia:

if (! openDataFile ())
  goto quit;

if (! getDataFromFile ())
  goto closeFileAndQuit;

if (! assignateSomeResources)
  goto freeResourcesAndQuit;

// Zrób więcej tutaj ....

freeResourcesAndQuit:
   // wolne zasoby
closeFileAndQuit:
   // zamknąć plik
porzucić:
   // porzucić!

Chciałbym wymienić to z zestawem funkcji: freeResourcesCloseFileQuit, closeFileQuit, i quit.
RCIX

4
Jeśli utworzyłeś te 3 dodatkowe funkcje, musisz przekazać wskaźniki / uchwyty do zasobów i plików, które mają być zwolnione / zamknięte. Prawdopodobnie wywołałbyś freeResourcesCloseFileQuit () wywołanie closeFileQuit (), co z kolei wywołałoby quit (). Teraz masz do utrzymania 4 ściśle powiązane funkcje, a 3 z nich prawdopodobnie zostaną wywołane najwyżej raz: z pojedynczej funkcji powyżej. Jeśli nalegasz na unikanie goto, IMO, zagnieżdżone bloki if () mają mniej narzutów i są łatwiejsze do odczytania i utrzymania. Co zyskujesz dzięki 3 dodatkowym funkcjom?
Adam Liss,

7

@ fizzer.myopenid.com: Twój opublikowany fragment kodu jest równoważny z następującym:

    while (system_call() == -1)
    {
        if (errno != EINTR)
        {
            // handle real errors

            break;
        }
    }

Zdecydowanie wolę tę formę.


1
OK, wiem, że instrukcja break jest po prostu bardziej akceptowalną formą goto ..
Mitch Wheat

10
Dla moich oczu to zagmatwane. Jest to pętla, której nie spodziewasz się wprowadzić w normalnej ścieżce wykonywania, więc logika wydaje się odwrotna. Jednak kwestia opinii.
fizzer

1
Wstecz? Zaczyna się, kontynuuje, przepada. Myślę, że ta forma bardziej wyraźnie określa swoje intencje.
Mitch Wheat

8
Zgodziłbym się tutaj z fizzerem, że goto zapewnia jaśniejsze oczekiwanie co do wartości warunku.
Marcus Griep

4
W jaki sposób ten fragment jest łatwiejszy do odczytania niż fizzer.
erikkallen

6

Mimo że z czasem nienawidzę tego wzorca, jest on zakorzeniony w programowaniu COM.

#define IfFailGo(x) {hr = (x); if (FAILED(hr)) goto Error}
...
HRESULT SomeMethod(IFoo* pFoo) {
  HRESULT hr = S_OK;
  IfFailGo( pFoo->PerformAction() );
  IfFailGo( pFoo->SomeOtherAction() );
Error:
  return hr;
}

5

Oto przykład dobrego goto:

// No Code

2
eee, nie ma gotów? (nieudana próba śmieszności: d)
moogs

9
To nie jest zbyt pomocne
Joseph

Myślałem, że to było zabawne. Dobra robota. +1
Fantastyczny pan Fox

@FlySwat FYI: Znaleziono to w kolejce recenzji niskiej jakości. Rozważ edycję.
Paul

1

Widziałem, że goto jest używany poprawnie, ale sytuacje są normalne brzydkie. Dzieje się tak tylko wtedy, gdy użycie gotosamego siebie jest o wiele gorsze niż oryginał. @Johnathon Holland, lęk, że twoja wersja jest mniej wyraźna. ludzie wydają się bać zmiennych lokalnych:

void foo()
{
    bool doAsuccess = doA();
    bool doBsuccess = doAsuccess && doB();
    bool doCsuccess = doBsuccess && doC();

    if (!doCsuccess)
    {
        if (doBsuccess)
            undoB();
        if (doAsuccess)
            undoA();
    }
}

Wolę takie pętle, ale niektórzy wolą while(true).

for (;;)
{
    //code goes here
}

2
Nigdy, przenigdy nie porównuj wartości logicznej z prawdą lub fałszem. To już jest wartość logiczna; po prostu użyj "doBsuccess = doAsuccess && doB ();" i zamiast tego „if (! doCsuccess) {}”. Jest prostszy i chroni Cię przed sprytnymi programistami, którzy piszą takie rzeczy, jak „#define true 0”, ponieważ, no cóż, mogą. Chroni również przed programistami, którzy mieszają wartości typu int i bool, a następnie zakładają, że każda wartość niezerowa jest prawdą.
Adam Liss

Dziękuję i == trueusunięty punkt . I tak jest bliżej mojego prawdziwego stylu. :)
Charles Beattie

0

Moim problemem jest to, że tracisz zakres blokowy; wszelkie zmienne lokalne zadeklarowane między gotos pozostają w mocy, jeśli pętla zostanie kiedykolwiek przerwana. (Może zakładasz, że pętla będzie trwać wiecznie; nie sądzę jednak, że to właśnie zadawał autor pytania).

Problem z określaniem zakresu jest bardziej problemem w C ++, ponieważ niektóre obiekty mogą zależeć od wywołania ich dtora w odpowiednim czasie.

Dla mnie najlepszym powodem używania goto jest wieloetapowy proces inicjalizacji, w którym ważne jest, aby wszystkie inity zostały wycofane, jeśli jeden z nich zawiedzie, a la:

if(!foo_init())
  goto bye;

if(!bar_init())
  goto foo_bye;

if(!xyzzy_init())
  goto bar_bye;

return TRUE;

bar_bye:
 bar_terminate();

foo_bye:
  foo_terminate();

bye:
  return FALSE;

0

Sam nie używam goto, ale pracowałem kiedyś z osobą, która używała ich w określonych przypadkach. Jeśli dobrze pamiętam, jego uzasadnienie dotyczyło problemów z wydajnością - miał też określone zasady, jak . Zawsze w tej samej funkcji, a etykieta zawsze znajdowała się PONIŻEJ instrukcji goto.


4
Dodatkowe dane: pamiętaj, że nie możesz użyć goto, aby wyjść poza funkcję, i że w C ++ zabrania się ominięcia konstrukcji obiektu za pomocą goto
paercebal

0
#include <stdio.h>
#include <string.h>

int main()
{
    char name[64];
    char url[80]; /*The final url name with http://www..com*/
    char *pName;
    int x;

    pName = name;

    INPUT:
    printf("\nWrite the name of a web page (Without www, http, .com) ");
    gets(name);

    for(x=0;x<=(strlen(name));x++)
        if(*(pName+0) == '\0' || *(pName+x) == ' ')
        {
            printf("Name blank or with spaces!");
            getch();
            system("cls");
            goto INPUT;
        }

    strcpy(url,"http://www.");
    strcat(url,name);
    strcat(url,".com");

    printf("%s",url);
    return(0);
}

-1

@Greg:

Dlaczego nie zrobić takiego przykładu:

void foo()
{
    if (doA())
    {    
        if (doB())
        {
          if (!doC())
          {
             UndoA();
             UndoB();
          }
        }
        else
        {
          UndoA();
        }
    }
    return;
}

13
Dodaje niepotrzebne poziomy wcięć, które mogą być naprawdę skomplikowane, jeśli masz dużą liczbę możliwych trybów awarii.
Adam Rosenfield

6
Każdego dnia wziąłbym wcięcia.
FlySwat

13
Ponadto zawiera znacznie więcej wierszy kodu, ma nadmiarowe wywołanie funkcji w jednym z przypadków i znacznie trudniej jest poprawnie dodać doD.
Patrick

6
Zgadzam się z Patrickiem - widziałem ten idiom używany w kilku warunkach zagnieżdżonych na głębokości około 8 poziomów. Bardzo paskudny. I bardzo podatny na błędy, zwłaszcza gdy próbujesz dodać coś nowego do miksu.
Michael Burr

11
Ten kod to po prostu krzyk „Będę miał błąd za kilka tygodni” ktoś zapomni coś cofnąć. Potrzebujesz całej swojej cofnięcia w tym samym miejscu. To jest jak „wreszcie” w rodzimych językach OO.
Ilya
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.