Beispiele für gute Gotos in C oder C ++ [geschlossen]

79

In diesem Thread sehen wir uns Beispiele für gute Verwendungen gotoin C oder C ++ an. Es ist inspiriert von einer Antwort, die die Leute gewählt haben, weil sie dachten, ich mache Witze.

Zusammenfassung (Bezeichnung vom Original geändert, um die Absicht noch deutlicher zu machen):

infinite_loop:

    // code goes here

goto infinite_loop;

Warum ist es besser als die Alternativen:

  • Es ist spezifisch. gotoist das Sprachkonstrukt, das einen bedingungslosen Zweig verursacht. Alternativen hängen von der Verwendung von Strukturen ab, die bedingte Verzweigungen mit einem entarteten, immer zutreffenden Zustand unterstützen.
  • Das Etikett dokumentiert die Absicht ohne zusätzliche Kommentare.
  • Der Leser muss den dazwischenliegenden Code nicht nach frühen breaks scannen (obwohl es für einen Hacker ohne Prinzipien immer noch möglich ist, continuemit einem frühen zu simulieren goto).

Regeln:

  • Stellen Sie sich vor, die Gotophoben hätten nicht gewonnen. Es versteht sich, dass das oben Genannte nicht in echtem Code verwendet werden kann, da es gegen etablierte Redewendungen verstößt.
  • Nehmen wir an, wir haben alle von "Goto als schädlich angesehen" gehört und wissen, dass goto zum Schreiben von Spaghetti-Code verwendet werden kann.
  • Wenn Sie mit einem Beispiel nicht einverstanden sind, kritisieren Sie es nur aus technischen Gründen ("Weil die Leute goto nicht mögen" ist kein technischer Grund).

Mal sehen, ob wir wie Erwachsene darüber sprechen können.

Bearbeiten

Diese Frage scheint jetzt erledigt zu sein. Es wurden einige qualitativ hochwertige Antworten generiert. Vielen Dank an alle, besonders an diejenigen, die mein kleines Loop-Beispiel ernst genommen haben. Die meisten Skeptiker waren besorgt über den fehlenden Blockumfang. Wie @quinmars in einem Kommentar hervorhob, können Sie den Loop-Body immer mit geschweiften Klammern versehen. Ich stelle dies nebenbei fest for(;;)und while(true)gebe Ihnen die Zahnspange auch nicht kostenlos ( und wenn Sie sie weglassen, kann dies zu ärgerlichen Fehlern führen). Wie auch immer, ich werde nicht mehr von Ihrer Gehirnleistung für diese Kleinigkeit verschwenden - ich kann mit den harmlosen und idiomatischen for(;;)und while(true)(genauso gut, wenn ich meinen Job behalten will) leben.

In Anbetracht der anderen Antworten sehe ich, dass viele Leute dies gotoals etwas ansehen , das Sie immer auf eine andere Weise umschreiben müssen. Natürlich können Sie dies vermeiden, gotoindem Sie eine Schleife, ein zusätzliches Flag, einen Stapel verschachtelter ifs oder was auch immer einführen , aber warum nicht überlegen, ob dies gotovielleicht das beste Werkzeug für den Job ist? Anders ausgedrückt, wie viel Hässlichkeit sind die Menschen bereit zu ertragen, um zu vermeiden, dass eine integrierte Sprachfunktion für den beabsichtigten Zweck verwendet wird? Meiner Meinung nach ist das Hinzufügen einer Flagge zu teuer, um sie zu bezahlen. Ich mag es, wenn meine Variablen Dinge in den Problem- oder Lösungsbereichen darstellen. "Nur um ein zu vermeiden goto" schneidet es nicht.

Ich akzeptiere die erste Antwort, die das C-Muster für die Verzweigung zu einem Bereinigungsblock ergab. IMO, dies ist das stärkste Argument für eine gotoder veröffentlichten Antworten, sicherlich wenn Sie es an den Verzerrungen messen, die ein Hasser durchmachen muss, um es zu vermeiden.

Sprudler
quelle
11
Ich verstehe nicht, warum die Gotophoben nicht einfach "#define goto report_to_your_supervisor_for_re_education_through_labour" oben in der Include-Datei des Projekts vorschreiben. Wenn es immer falsch ist, machen Sie es unmöglich. Ansonsten ist es manchmal richtig ...
Steve Jessop
14
"Kann nicht in echtem Code verwendet werden"? Ich benutze es in "echtem" Code immer dann, wenn es das beste Werkzeug für den Job ist. "Established Idiom" ist ein schöner Euphemismus für "blinden Dogmatismus".
Dan Moulding
7
Ich bin damit einverstanden, dass "goto" nützlich sein kann (es gibt unten großartige Beispiele), aber ich bin mit Ihrem spezifischen Beispiel nicht einverstanden. Eine Zeile mit der Aufschrift "goto infinite_loop" bedeutet "gehe zu dem Teil des Codes, in dem wir für immer mit dem Looping beginnen", wie in "initialize (); set_things_up (); goto infinite_loop;" Wenn Sie wirklich vermitteln möchten, dass Sie mit der nächsten Iteration der Schleife beginnen, in der wir uns bereits befinden, ist dies völlig anders. Wenn Sie versuchen, eine Schleife zu erstellen, und Ihre Sprache Konstrukte enthält, die speziell für die Schleife entwickelt wurden, verwenden Sie diese aus Gründen der Übersichtlichkeit. while (true) {foo ()} ist ziemlich eindeutig.
Josh
6
Ein großer Nachteil Ihres Beispiels ist, dass nicht ersichtlich ist, an welchen Stellen im Code Sie möglicherweise zu diesem Label springen. Eine "normale" Endlosschleife ( for (;;)) hat keine überraschenden Einstiegspunkte.
Jalf
3
@ György: ja, aber das ist kein Einstiegspunkt.
Jalf

Antworten:

87

Hier ist ein Trick, den ich von Leuten gehört habe, die ihn benutzen. Ich habe es aber noch nie in freier Wildbahn gesehen. Und es gilt nur für C, weil C ++ RAII hat, um dies idiomatischer zu tun.

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

    /* everything has succeeded */
    return;

cleanupB:
    undoB();
cleanupA:
    undoA();
exit:
    return;
}
Greg Rogers
quelle
6
Ich wusste, ich hätte diesen "Speed ​​Typing" -Kurs in der High School belegen sollen! Gute Antwort.
Adam Liss
6
Die zusätzlichen Blöcke verursachen unnötige Einrückungen, die schwer zu lesen sind. Es funktioniert auch nicht, wenn sich eine der Bedingungen in einer Schleife befindet.
Adam Rosenfield
26
Sie können viel von dieser Art von Code in den meisten Unix-Dingen auf niedriger Ebene sehen (wie zum Beispiel im Linux-Kernel). In C ist dies die beste Redewendung für die Fehlerbehebung IMHO.
David Cournapeau
8
Wie bereits erwähnt, verwendet der Linux-Kernel diesen Stil ständig .
CesarB
8
Nicht nur der Linux-Kernel sieht sich die Windows-Treiberbeispiele von Microsoft an, sondern auch das gleiche Muster. Im Allgemeinen ist dies eine C-Methode, um Ausnahmen zu behandeln, und eine sehr nützliche :). Normalerweise bevorzuge ich nur 1 Etikett und in einigen Fällen 2. 3 kann in 99% der Fälle vermieden werden.
Ilya
85

Das klassische Bedürfnis nach GOTO in C ist wie folgt

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

final:

Es gibt keine einfache Möglichkeit, aus verschachtelten Schleifen ohne goto auszubrechen.

Paul Nathan
quelle
49
Wenn dieses Bedürfnis auftaucht, kann ich meistens eine Rückgabe verwenden. Sie müssen jedoch kleine Funktionen schreiben, damit dies funktioniert.
Darius Bacon
7
Ich stimme Darius definitiv zu - überarbeite es zu einer Funktion und kehre stattdessen zurück!
Metao
9
@metao: Bjarne Stroustrup nicht einverstanden. In seinem C ++ - Programmiersprachenbuch ist dies genau das Beispiel für eine "gute Verwendung" von goto.
Paercebal
19
@Adam Rosenfield: Das ganze Problem mit goto, das von Dijkstra hervorgehoben wurde, ist, dass strukturierte Programmierung verständlichere Konstrukte bietet. Wenn Sie das Lesen von Code erschweren, um zu vermeiden, dass der Autor diesen Aufsatz nicht versteht ...
Steve Jessop,
5
@Steve Jessop: Nicht nur, dass die Leute den Aufsatz missverstanden haben, die meisten Kultisten verstehen den historischen Kontext, in dem er geschrieben wurde, nicht.
NUR MEINE korrekte MEINUNG
32

Hier ist mein nicht dummes Beispiel (von Stevens APITUE) für Unix-Systemaufrufe, die möglicherweise durch ein Signal unterbrochen werden.

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

        // handle real errors
    }

Die Alternative ist eine entartete Schleife. Diese Version liest sich wie Englisch "Wenn der Systemaufruf durch ein Signal unterbrochen wurde, starten Sie es neu".

Sprudler
quelle
3
Diese Methode wird zum Beispiel verwendet. Linux Scheduler haben ein solches Goto, aber ich würde sagen, es gibt nur sehr wenige Fälle, in denen Rückwärts-Goto akzeptabel sind und im Allgemeinen vermieden werden sollten.
Ilya
8
@Amarghosh, continueist nur gotoeine Maske.
Jball
2
@jball ... hmm .. aus Gründen der Argumentation, ja; Sie können mit goto und Spaghetti-Code mit continue einen gut lesbaren Code erstellen. Letztendlich hängt dies von der Person ab, die den Code schreibt. Der Punkt ist, dass es leicht ist, sich mit goto zu verirren als mit continue. Und Neulinge verwenden normalerweise den ersten Hammer, den sie für jedes Problem bekommen.
Amarghosh
2
@Amarghosh. Ihr "verbesserter" Code entspricht nicht einmal dem Original - er wird im Erfolgsfall für immer wiederholt.
Fizzer
3
@fizzer oopsie .. es werden zwei else breaks benötigt (eines für jedes ifs), um es gleichwertig zu machen .. was soll ich sagen ... gotooder nicht, dein Code ist nur so gut wie du :(
Amarghosh
14

Wenn Duffs Gerät kein Goto braucht, sollten Sie es auch nicht! ;)

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);
    }
}

Code oben aus Wikipedia- Eintrag .

Mitch Wheat
quelle
2
Cmon Leute, wenn ihr abstimmen wollt, ein kurzer Kommentar, warum das großartig wäre. :)
Mitch Wheat
10
Dies ist ein Beispiel für einen logischen Irrtum, der auch als "Appell an die Autorität" bezeichnet wird. Schau es dir auf Wikipedia an.
Marcus Griep
11
Humor wird hier oft nicht geschätzt ...
Steven A. Lowe
8
macht Duffs Gerät Bier?
Steven A. Lowe
6
Dieser kann 8 auf einmal machen ;-)
Jasper Bekkers
14

Knuth hat einen Artikel "Strukturierte Programmierung mit GOTO-Anweisungen" geschrieben, den Sie zB hier erhalten können . Dort finden Sie viele Beispiele.

zvrba
quelle
Dieses Papier sieht tief aus. Ich werde es aber lesen. Danke
Fizzer
2
Das Papier ist so veraltet, dass es nicht einmal lustig ist. Knuths Annahmen dort gelten einfach nicht mehr.
Konrad Rudolph
3
Welche Annahmen? Seine Beispiele sind heute für eine prozedurale Sprache so real wie C (er gibt sie in einem Pseudocode an) wie damals.
Zvrba
8
Ich bin enttäuscht, dass du nicht geschrieben hast: Du kannst 'hier' gehen, um es zu bekommen, das Wortspiel für mich wäre zu unerträglich gewesen, es nicht zu machen!
RhysW
12

Sehr gewöhnlich.

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;
}

Der einzige Fall, den ich jemals benutze, gotoist das Vorwärtsspringen, normalerweise aus Blöcken und niemals in Blöcke. Dies vermeidet den Missbrauch do{}while(0)und anderer Konstrukte, die die Verschachtelung erhöhen, während lesbarer, strukturierter Code erhalten bleibt.

kurzlebig
quelle
5
Ich denke, dies ist die typische C-Methode zur Fehlerbehandlung. Ich kann nicht sehen, dass es schön ersetzt wurde, besser lesbar auf andere Weise Grüße
Friedrich
Warum nicht ... do_stuff(thingy) { RAIILockerObject(thingy); ..... /* -->*/ } /* <---*/ :)
22етър Петров
1
@ ПетърПетров Das ist ein Muster in C ++, das kein Analogon in C. hat
Ephemient
Oder in einer noch strukturierteren Sprache als C ++, try { lock();..code.. } finally { unlock(); }aber ja, C hat kein Äquivalent.
Blindy
12

Ich habe nichts gegen gotos im Allgemeinen, aber ich kann mir mehrere Gründe vorstellen, warum Sie sie nicht für eine Schleife verwenden möchten, wie Sie es erwähnt haben:

  • Der Umfang wird nicht eingeschränkt, daher werden alle temporären Variablen, die Sie im Inneren verwenden, erst später freigegeben.
  • Der Umfang wird nicht eingeschränkt, daher kann es zu Fehlern kommen.
  • Der Bereich wird nicht eingeschränkt, daher können Sie dieselben Variablennamen später in zukünftigen Codes im selben Bereich nicht mehr verwenden.
  • Der Umfang wird dadurch nicht eingeschränkt, sodass Sie die Möglichkeit haben, eine Variablendeklaration zu überspringen.
  • Die Leute sind nicht daran gewöhnt und es wird Ihren Code schwerer lesbar machen.
  • Verschachtelte Schleifen dieses Typs können zu Spaghetti-Code führen, normale Schleifen führen nicht zu Spaghetti-Code.
Brian R. Bondy
quelle
1
ist inf_loop: {/ * loop body * /} gehe zu inf_loop; besser? :)
Quinmars
2
Die Punkte 1 bis 4 sind für dieses Beispiel auch ohne Klammern zweifelhaft . 1 Es ist eine Endlosschleife. 2 Zu vage, um sie anzusprechen. 3 Der Versuch, eine gleichnamige Variable in einem umschließenden Bereich auszublenden, ist eine schlechte Praxis. 4. Ein Rückwärtsgang kann eine Deklaration nicht überspringen.
Fizzer
1-4 Wie bei allen Endlosschleifen haben Sie normalerweise eine Art Unterbrechungsbedingung in der Mitte. Sie sind also anwendbar. Re a bakward goto kann eine Erklärung nicht überspringen ... nicht alle gotos sind rückwärts ...
Brian R. Bondy
7

Ein guter Ort, um ein goto zu verwenden, ist ein Vorgang, der an mehreren Punkten abgebrochen werden kann, von denen jeder verschiedene Bereinigungsstufen erfordert. Gotophobe können die Gotos immer durch strukturierten Code und eine Reihe von Tests ersetzen, aber ich denke, dies ist einfacher, da übermäßige Einrückungen vermieden werden:

if (! openDataFile ())
  gehe aufhören;

if (! getDataFromFile ())
  gehe zu closeFileAndQuit;

if (! allocateSomeResources)
  gehe zu freeResourcesAndQuit;

// Mach hier mehr Arbeit ....

freeResourcesAndQuit:
   // freie Ressourcen
closeFileAndQuit:
   // Datei schließen
Verlassen:
   // Verlassen!
Adam Liss
quelle
Ich würde dies mit einer Reihe von Funktionen ersetzen: freeResourcesCloseFileQuit, closeFileQuit, und quit.
RCIX
4
Wenn Sie diese 3 zusätzlichen Funktionen erstellt haben, müssen Sie Zeiger / Handles an die Ressourcen und Dateien übergeben, die freigegeben / geschlossen werden sollen. Sie würden wahrscheinlich veranlassen, dass freeResourcesCloseFileQuit () closeFileQuit () aufruft, was wiederum quit () aufruft. Jetzt müssen Sie 4 eng gekoppelte Funktionen beibehalten, von denen 3 wahrscheinlich höchstens einmal aufgerufen werden: von der obigen Einzelfunktion. Wenn Sie darauf bestehen, goto, IMO, verschachtelte if () -Blöcke zu vermeiden, haben sie weniger Overhead und sind einfacher zu lesen und zu warten. Was gewinnen Sie mit 3 Zusatzfunktionen?
Adam Liss
7

@ fizzer.myopenid.com: Ihr geposteter Code-Ausschnitt entspricht dem Folgenden:

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

            break;
        }
    }

Ich bevorzuge definitiv diese Form.

Mitch Wheat
quelle
1
OK, ich weiß, dass die break-Anweisung nur eine akzeptablere Form von goto ist.
Mitch Wheat
10
Für meine Augen ist es verwirrend. Es ist eine Schleife, von der Sie nicht erwarten, dass sie in den normalen Ausführungspfad eintritt, daher scheint die Logik rückwärts zu sein. Meinungssache.
Fizzer
1
Rückwärts? Es beginnt, es geht weiter, es fällt durch. Ich denke, diese Form gibt ihre Absichten offensichtlicher wieder.
Mitch Wheat
8
Ich würde Fizzer hier zustimmen, dass das goto eine klarere Erwartung hinsichtlich des Wertes der Bedingung liefert.
Marcus Griep
4
Auf welche Weise ist dieses Snippet leichter zu lesen als das von Fizzer?
Erikkallen
6

Obwohl ich dieses Muster im Laufe der Zeit immer mehr hasse, ist es in die COM-Programmierung integriert.

#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;
}
JaredPar
quelle
5

Hier ist ein Beispiel für ein gutes Goto:

// No Code
FlySwat
quelle
2
ähm, keine gotos? (erfolgloser Versuch lustig zu machen: d)
Moogs
9
Dies ist nicht wirklich hilfreich
Joseph
Ich fand das lustig. Gut gemacht. +1
Fantastischer Herr Fox
@FlySwat FYI: Fand dies in der Warteschlange für Überprüfungen von geringer Qualität. Betrachten Sie die Bearbeitung.
Paul
1

Ich habe gesehen, wie ich richtig eingesetzt wurde, aber die Situationen sind normalerweise hässlich. Es ist nur, wenn der Gebrauch von sich gotoselbst so viel weniger schlecht ist als das Original. @ Johnathon Holland das Problem ist, dass Ihre Version weniger klar ist. Menschen scheinen Angst vor lokalen Variablen zu haben:

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

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

Und ich bevorzuge solche Loops, aber manche Leute bevorzugen es while(true) .

for (;;)
{
    //code goes here
}
Charles Beattie
quelle
2
Vergleichen Sie niemals einen booleschen Wert mit wahr oder falsch. Es ist schon ein Boolescher Wert; Verwenden Sie einfach "doBsuccess = doAsuccess && doB ();" und stattdessen "if (! doCsuccess) {}". Es ist einfacher und schützt Sie vor cleveren Programmierern, die Dinge wie "#define true 0" schreiben, weil sie es können. Es schützt Sie auch vor Programmierern, die Ints und Bools mischen und dann davon ausgehen, dass ein Wert ungleich Null wahr ist.
Adam Liss
Danke und Punkt == trueentfernt. Das kommt meinem wirklichen Stil sowieso näher. :)
Charles Beattie
0

Mein Problem dabei ist, dass Sie das Block Scoping verlieren. Alle zwischen den gotos deklarierten lokalen Variablen bleiben in Kraft, falls die Schleife jemals unterbrochen wird. (Vielleicht nehmen Sie an, dass die Schleife für immer läuft. Ich glaube jedoch nicht, dass der ursprüngliche Fragesteller dies gestellt hat.)

Das Problem des Scoping ist bei C ++ eher ein Problem, da einige Objekte möglicherweise davon abhängen, ob ihr dtor zu geeigneten Zeiten aufgerufen wird.

Für mich ist der beste Grund, goto zu verwenden, ein mehrstufiger Initialisierungsprozess, bei dem es wichtig ist, dass alle Inits zurückgesetzt werden, wenn einer fehlschlägt.

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;
Jim Nelson
quelle
0

Ich benutze goto's selbst nicht, aber ich habe einmal mit einer Person gearbeitet, die sie in bestimmten Fällen benutzt hat. Wenn ich mich richtig erinnere, lag sein Grundprinzip bei Leistungsproblemen - er hatte auch spezifische Regeln dafür, wie . Immer in der gleichen Funktion, und das Label war immer UNTER der goto-Anweisung.

user25306
quelle
4
Weitere Angaben: Beachten Sie, dass Sie nicht gehe verwenden außerhalb der Funktion zu gehen, und das in C ++ ist es verboten , in den Bypass ein Objekt Konstruktion durch eine 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);
}
StrifeSephiroth
quelle
-1

@ Greg:

Warum machen Sie Ihr Beispiel nicht so:

void foo()
{
    if (doA())
    {    
        if (doB())
        {
          if (!doC())
          {
             UndoA();
             UndoB();
          }
        }
        else
        {
          UndoA();
        }
    }
    return;
}
FlySwat
quelle
13
Es fügt unnötige Einrückungsstufen hinzu, die sehr haarig werden können, wenn Sie eine große Anzahl möglicher Fehlermodi haben.
Adam Rosenfield
6
Ich würde jeden Tag Einrückungen vornehmen.
FlySwat
13
Außerdem sind es viel mehr Codezeilen, es gibt in einem der Fälle einen redundanten Funktionsaufruf und es ist weitaus schwieriger, ein doD korrekt hinzuzufügen.
Patrick
6
Ich stimme Patrick zu - ich habe gesehen, dass diese Redewendung unter verschiedenen Bedingungen verwendet wurde, die bis zu einer Tiefe von etwa 8 Ebenen verschachtelt waren. Sehr böse. Und sehr fehleranfällig, insbesondere wenn versucht wird, der Mischung etwas Neues hinzuzufügen.
Michael Burr
11
Dieser Code ist nur ein Schrei "Ich werde in ein paar Wochen einen Fehler haben". Jemand wird vergessen, etwas rückgängig zu machen. Sie müssen alle Rückgängigmachungen an derselben Stelle vornehmen. Es ist wie "endlich" in OO-Muttersprachen.
Ilya