Frage

Jeder kennt Dijkstra Briefe an die Redaktion:Gehe zu Aussage, die als schädlich gilt (Auch Hier .html-Transkript und Hier .pdf) und seitdem gibt es starke Bestrebungen, die goto-Anweisung wann immer möglich zu vermeiden.Obwohl es möglich ist, goto zu verwenden, um nicht wartbaren, ausgedehnten Code zu erstellen, bleibt er dennoch erhalten moderne Programmiersprachen.Sogar die Fortgeschrittenen Fortsetzung Die Kontrollstruktur in Scheme kann als anspruchsvolles Goto beschrieben werden.

Welche Umstände rechtfertigen die Verwendung von goto?Wann sollte man es am besten vermeiden?

Als Folgefrage:C bietet ein Paar Funktionen, setjmp und longjmp, die die Möglichkeit bieten, nicht nur innerhalb des aktuellen Stack-Frames, sondern auch innerhalb aller aufrufenden Frames zu wechseln.Sollten diese als genauso gefährlich angesehen werden wie Goto?Gefährlicher?


Dijkstra selbst bedauerte diesen Titel, für den er nicht verantwortlich war.Am Ende von EWD1308 (Auch Hier .pdf) schrieb er:

Zum Schluss noch eine Kurzgeschichte fürs Protokoll.1968 veröffentlichte die Kommunikation des ACM einen Text von mir unter dem Titel "Die GOTO -Aussage wurde als schädlich angesehen", auf die in späteren Jahren am häufigsten referenziert werden würde, aber oft von Autoren, die nicht mehr davon gesehen hatten als seinen Titel, was zu einem Eckpfeiler meines Ruhmes wurde, indem er eine Vorlage wurde:Wir würden alle möglichen Artikel unter dem Titel "X als schädlich" für fast jedes X sehen, einschließlich eines mit dem Titel "Dijkstra gilt als schädlich".Aber was war passiert?Ich hatte ein Papier unter dem Titel eingereicht "Ein Fall gegen die GOTO -Erklärung"Um seine Veröffentlichung zu beschleunigen, hatte der Herausgeber einen" Brief an den Herausgeber "und dabei einen neuen Titel seiner eigenen Erfindung gegeben!Der Herausgeber war Niklaus Wirth.

Ein gut durchdachter klassischer Aufsatz zu diesem Thema, der mit dem von Dijkstra vergleichbar ist, ist Strukturierte Programmierung mit go to-Anweisungen, von Donald E.Knuth.Die Lektüre beider Texte hilft dabei, den Kontext und ein undogmatisches Verständnis des Themas wiederherzustellen.In diesem Artikel wird Dijkstras Meinung zu diesem Fall dargelegt und ist noch deutlicher:

Donald E.Knuth: Ich glaube, dass ich durch die Präsentation einer solchen Ansicht nicht mit Dijkstras Ideen nicht stark einverstanden bin, da er kürzlich Folgendes geschrieben hat:"Bitte fallen Sie nicht in die Falle zu glauben, dass ich schrecklich dogmatisch in Bezug auf [die Aussage] bin. Ich habe das unangenehme Gefühl, dass andere eine Religion daraus machen, als ob die konzeptionellen Programme der Programmierung durch einen einzigen Trick durch eine einfache Form der codierenden Disziplin gelöst werden könnten!"

War es hilfreich?

Lösung

Die folgenden Aussagen sind Verallgemeinerungen;Es ist zwar immer möglich, sich auf eine Ausnahme zu berufen, aber das ist (meiner Erfahrung nach und meiner bescheidenen Meinung nach) das Risiko normalerweise nicht wert.

  1. Die uneingeschränkte Verwendung von Speicheradressen (entweder GOTO oder Rohzeiger) bietet zu viele Möglichkeiten, leicht vermeidbare Fehler zu machen.
  2. Je mehr Möglichkeiten es gibt, zu einer bestimmten „Position“ im Code zu gelangen, desto weniger sicher kann man sein, wie sich das System an diesem Punkt befindet.(Siehe unten.)
  3. Bei der strukturierten Programmierung geht es meiner Meinung nach weniger darum, „GOTOs zu vermeiden“, als vielmehr darum, die Struktur des Codes an die Struktur der Daten anzupassen.Beispielsweise eine sich wiederholende Datenstruktur (z. B.Array, sequentielle Datei usw.) wird natürlich von einer wiederholten Codeeinheit verarbeitet.Mit eingebauten Strukturen (z.B.while, for, Until, for-each usw.) ermöglicht es dem Programmierer, die Langeweile zu vermeiden, dieselben klischeehaften Codemuster zu wiederholen.
  4. Auch wenn es sich bei GOTO um Implementierungsdetails auf niedriger Ebene handelt (was nicht immer der Fall ist!), liegt es unter dem Niveau, über das der Programmierer nachdenken sollte.Wie viele Programmierer gleichen ihre persönlichen Scheckbücher in roher Binärdatei aus?Wie viele Programmierer kümmern sich darum, welcher Sektor auf der Festplatte einen bestimmten Datensatz enthält, anstatt nur einen Schlüssel für eine Datenbank-Engine bereitzustellen (und wie viele Möglichkeiten könnten schief gehen, wenn wir alle unsere Programme wirklich in Bezug auf physische Festplattensektoren schreiben würden)?

Fußnoten zu oben:

Berücksichtigen Sie zu Punkt 2 den folgenden Code:

a = b + 1
/* do something with a */

An der Stelle „etwas tun“ im Code können wir das mit großer Sicherheit sagen a ist größer als b.(Ja, ich ignoriere die Möglichkeit eines nicht abgefangenen Ganzzahlüberlaufs.Lassen wir uns nicht mit einem einfachen Beispiel verzetteln.)

Wenn der Code andererseits so lauten würde:

...
goto 10
...
a = b + 1
10: /* do something with a */
...
goto 10
...

Die Vielfalt der Möglichkeiten, zu Etikett 10 zu gelangen, bedeutet, dass wir viel härter arbeiten müssen, um Vertrauen in die Beziehungen zwischen ihnen zu haben a Und b an diesem Punkt.(Tatsächlich ist es im allgemeinen Fall unentscheidbar!)

Was Punkt 4 betrifft, ist der gesamte Begriff „irgendwohin gehen“ im Code nur eine Metapher.Außer Elektronen und Photonen (für die Abwärme) „wandert“ nichts wirklich irgendwo in der CPU.Manchmal geben wir eine Metapher für eine andere, nützlichere auf.Ich erinnere mich, dass ich (vor ein paar Jahrzehnten!) einer Sprache begegnet bin, in der

if (some condition) {
  action-1
} else {
  action-2
}

wurde auf einer virtuellen Maschine implementiert, indem Aktion-1 und Aktion-2 als out-of-line-parameterlose Routinen kompiliert und dann ein einzelner VM-Opcode mit zwei Argumenten verwendet wurde, der den booleschen Wert der Bedingung verwendete, um die eine oder andere aufzurufen.Das Konzept lautete einfach „wählen Sie, was Sie jetzt aufrufen möchten“ und nicht „gehen Sie hierher oder gehen Sie dorthin“.Auch hier nur eine Änderung der Metapher.

Andere Tipps

XKCD's GOTO Comic

Ein Kollege von mir sagte, der einzige Grund, ein GOTO zu verwenden, sei, wenn man sich so weit in eine Ecke programmiert habe, dass dies der einzige Ausweg sei.Mit anderen Worten: Richten Sie den Entwurf im Voraus ein und Sie müssen später kein GOTO verwenden.

Ich dachte, dieser Comic zeigt, dass "ich den Programm des Programms umstrukturieren oder stattdessen ein kleines" Goto "verwenden könnte. Ein Goto ist ein schwacher Ausweg, wenn Sie schwaches Design haben. Velociraptoren jagen die Schwachen.

Manchmal ist es sinnvoll, GOTO als Alternative zur Ausnahmebehandlung innerhalb einer einzelnen Funktion zu verwenden:

if (f() == false) goto err_cleanup;
if (g() == false) goto err_cleanup;
if (h() == false) goto err_cleanup;

return;

err_cleanup:
...

COM-Code scheint ziemlich oft in dieses Muster zu fallen.

Ich kann mich nur daran erinnern, dass ich einmal „goto“ verwendet habe.Ich hatte eine Reihe von fünf verschachtelten, gezählten Schleifen und musste aufgrund bestimmter Bedingungen in der Lage sein, frühzeitig von innen aus der gesamten Struktur auszubrechen:

for{
  for{
    for{
      for{
        for{
          if(stuff){
            GOTO ENDOFLOOPS;
          }
        }
      }
    }
  }
}

ENDOFLOOPS:

Ich hätte einfach eine boolesche Unterbrechungsvariable deklarieren und sie als Teil der Bedingung für jede Schleife verwenden können, aber in diesem Fall entschied ich, dass ein GOTO genauso praktisch und genauso lesbar war.

Kein Velociraptor hat mich angegriffen.

Das hatten wir schon Diskussion und ich stehe dabei mein Punkt.

Außerdem habe ich es satt, dass Leute höhere Sprachstrukturen als „goto in Verkleidung“, weil sie offensichtlich nicht verstanden haben, worauf es ankommt überhaupt.Zum Beispiel:

Sogar die erweiterte Fortsetzungskontrollstruktur in Scheme kann als ausgefeiltes Goto beschrieben werden.

Das ist völliger Unsinn. Jeden Die Kontrollstruktur kann in Bezug auf implementiert werden goto aber diese Beobachtung ist völlig trivial und nutzlos. goto wird nicht wegen seiner positiven Auswirkungen als schädlich angesehen, sondern wegen seiner negativen Folgen, die durch strukturierte Programmierung beseitigt wurden.

Ebenso ist die Aussage „GOTO ist ein Werkzeug, und wie alle Werkzeuge kann es verwendet und missbraucht werden“ völlig daneben.Kein moderner Bauarbeiter würde einen Stein benutzen und behaupten, dass es „ein Werkzeug ist“. Felsen wurden durch Hämmer ersetzt. goto wurde durch Kontrollstrukturen ersetzt.Wenn der Bauarbeiter ohne Hammer in der Wildnis stranden würde, würde er natürlich stattdessen einen Stein benutzen.Wenn ein Programmierer eine minderwertige Programmiersprache verwenden muss, die nicht über Feature X verfügt, muss er diese natürlich verwenden goto stattdessen.Wenn sie es jedoch irgendwo anders anstelle des entsprechenden Sprachmerkmals verwendet, hat sie die Sprache offensichtlich nicht richtig verstanden und verwendet sie falsch.So einfach ist das wirklich.

Goto steht auf meiner Liste der Dinge, die ich einfach so in ein Programm aufnehmen sollte, ganz weit unten.Das bedeutet nicht, dass es inakzeptabel ist.

Goto kann für Zustandsmaschinen nützlich sein.Eine switch-Anweisung in einer Schleife lautet (in der Reihenfolge ihrer typischen Wichtigkeit):(a) nicht wirklich repräsentativ für den Kontrollfluss, (b) hässlich, (c) je nach Sprache und Compiler möglicherweise ineffizient.Sie schreiben also am Ende eine Funktion pro Zustand und tun Dinge wie "return Next_state"; die sogar wie Goto aussehen.

Zugegebenermaßen ist es schwierig, Zustandsautomaten so zu kodieren, dass sie leicht verständlich sind.Allerdings hat diese Schwierigkeit nichts mit der Verwendung von goto zu tun, und nichts davon kann durch die Verwendung alternativer Kontrollstrukturen verringert werden.Es sei denn, Ihre Sprache verfügt über ein „Zustandsmaschinen“-Konstrukt.Meins nicht.

In den seltenen Fällen, in denen Ihr Algorithmus am besten anhand eines Pfads durch eine Folge von Knoten (Zuständen) verständlich ist, die durch eine begrenzte Menge zulässiger Übergänge (Gotos) verbunden sind, und nicht durch einen spezifischeren Kontrollfluss (Schleifen, Bedingungen usw.). ), dann sollte das im Code explizit sein.Und Sie sollten ein hübsches Diagramm zeichnen.

setjmp/longjmp kann nützlich sein, um Ausnahmen oder ausnahmeähnliches Verhalten zu implementieren.Auch wenn Ausnahmen nicht allgemein gelobt werden, gelten sie im Allgemeinen als „gültige“ Kontrollstruktur.

setjmp/longjmp sind „gefährlicher“ als goto in dem Sinne, dass es schwieriger ist, sie richtig zu verwenden, ganz zu schweigen davon, dass sie verständlich sind.

Es gab noch nie eine Sprache, in der es am wenigsten schwierig ist, schlechten Code zu schreiben.– Donald Knuth.

Das Herausnehmen von goto aus C würde es nicht einfacher machen, guten Code in C zu schreiben.Tatsächlich würde es eher den Punkt verfehlen, den C ausmacht angeblich in der Lage zu sein, als verherrlichte Assemblersprache zu fungieren.

Als nächstes kommt „Hinweise, die als schädlich gelten“, dann „Ententippen, die als schädlich gelten“.Wer bleibt dann übrig, um Sie zu verteidigen, wenn sie Ihr unsicheres Programmierkonstrukt wegnehmen?Äh?

In Linux:Verwenden von goto im Kernel-Code Auf Kernel Trap gibt es eine Diskussion mit Linus Torvalds und einem „Neuen“ über die Verwendung von GOTOs im Linux-Code.Da gibt es einige sehr gute Punkte und Linus ist mit der üblichen Arroganz gekleidet :)

Einige Passagen:

Linus:"Nein, Sie wurden von CS -Leuten einer Gehirnwäsche unterzogen, die dachten, Niklaus Wirth wusste tatsächlich, wovon er sprach.Er tat es nicht.Er hat keinen kühlenden Hinweis. "

-

Linus:"Ich denke, dass Gotos in Ordnung sind und sie sind oft lesbarer als große Mengen an Einrückungen."

-

Linus:"Natürlich können Gotos in dummen Sprachen wie Pascal, in denen Etiketten nicht beschreibend sein können, schlecht sein."

In C, goto Funktioniert nur im Rahmen der aktuellen Funktion, wodurch mögliche Fehler lokalisiert werden. setjmp Und longjmp sind weitaus gefährlicher, da sie nicht lokal, kompliziert und von der Implementierung abhängig sind.In der Praxis sind sie jedoch zu undurchsichtig und ungewöhnlich, um viele Probleme zu verursachen.

Ich glaube, dass die Gefahr besteht goto in C ist stark übertrieben.Denken Sie daran, dass das Original goto Die Auseinandersetzungen fanden bereits zu Zeiten von Sprachen wie dem altmodischen BASIC statt, in denen Anfänger Spaghetti-Code wie diesen schrieben:

3420 IF A > 2 THEN GOTO 1430

Hier beschreibt Linus eine angemessene Verwendung von goto: http://www.kernel.org/doc/Documentation/CodingStyle (Kapitel 7).

Heutzutage ist es schwer, die große Sache daran zu erkennen GOTO Aussage, weil die Leute der „strukturierten Programmierung“ die Debatte größtenteils gewonnen haben und die heutigen Sprachen über genügend Kontrollflussstrukturen verfügen, die es zu vermeiden gilt GOTO.

Zählen Sie die Anzahl gotos in einem modernen C-Programm.Addieren Sie nun die Anzahl von break, continue, Und return Aussagen.Addieren Sie außerdem die Häufigkeit, mit der Sie es verwenden if, else, while, switch oder case.So viele sind es ungefähr GOTODas hätte Ihr Programm gehabt, wenn Sie 1968, als Dijkstra seinen Brief schrieb, in FORTRAN oder BASIC geschrieben hätten.

Den damaligen Programmiersprachen fehlte es an Kontrollfluss.Zum Beispiel im ursprünglichen Dartmouth BASIC:

  • IF Aussagen hatten nein ELSE.Wenn Sie eines wollten, mussten Sie schreiben:

    100 IF NOT condition THEN GOTO 200
    ...stuff to do if condition is true...
    190 GOTO 300
    200 REM else
    ...stuff to do if condition is false...
    300 REM end if
    
  • Auch wenn Ihr IF Aussage brauchte keine ELSE, war es immer noch auf eine einzige Zeile beschränkt, die normalerweise aus a bestand GOTO.

  • Es gab keine DO...LOOP Stellungnahme.Für Nicht-FOR Schleifen mussten Sie die Schleife mit einem expliziten beenden GOTO oder IF...GOTO Zurück zum Anfang.

  • Es gab keine SELECT CASE.Du musstest es benutzen ON...GOTO.

Am Ende hast du also eine viel von GOTOs in Ihrem Programm.Und man konnte sich nicht auf die Einschränkung verlassen GOTOs innerhalb einer einzelnen Unterroutine (weil GOSUB...RETURN war ein so schwaches Konzept von Unterprogrammen), also diese GOTOEs könnte gehen überall.Dies machte es offensichtlich schwierig, den Kontrollfluss zu verfolgen.

Hier ist die Anti-GOTO Bewegung kam von.

Go To kann in bestimmten Fällen eine Art Ersatz für die „echte“ Ausnahmebehandlung sein.Halten:

ptr = malloc(size);
if (!ptr) goto label_fail;
bytes_in = read(f_in,ptr,size);
if (bytes_in=<0) goto label_fail;
bytes_out = write(f_out,ptr,bytes_in);
if (bytes_out != bytes_in) goto label_fail;

Offensichtlich wurde dieser Code vereinfacht, um weniger Platz zu beanspruchen. Hängen Sie sich also nicht zu sehr auf die Details ein.Aber denken Sie über eine Alternative nach, die ich schon viel zu oft gesehen habe Produktion Code von Programmierern, die absurde Anstrengungen unternehmen, um die Verwendung von goto zu vermeiden:

success=false;
do {
    ptr = malloc(size);
    if (!ptr) break;
    bytes_in = read(f_in,ptr,size);
    if (count=<0) break;
    bytes_out = write(f_out,ptr,bytes_in);
    if (bytes_out != bytes_in) break;
    success = true;
} while (false);

Nun macht dieser Code funktionell genau das Gleiche.Tatsächlich ist der vom Compiler generierte Code nahezu identisch.Allerdings ist der Eifer des Programmierers zu besänftigen Nogoto (der gefürchtete Gott der akademischen Zurechtweisung), dieser Programmierer hat die zugrunde liegende Redewendung, die der while Die Schleife stellt eine reelle Zahl dar und gibt Aufschluss über die Lesbarkeit des Codes. Das ist nicht besser.

Die Moral der Geschichte lautet also: Wenn Sie feststellen, dass Sie auf etwas wirklich Dummes zurückgreifen, um die Verwendung von „goto“ zu vermeiden, dann tun Sie es nicht.

Donald E.Knuth beantwortete diese Frage im Buch „Literate Programming“, 1992 CSLI.Auf P.17 gibt es einen Aufsatz „Strukturierte Programmierung mit goto-Anweisungen" (PDF).Ich denke, der Artikel könnte auch in anderen Büchern veröffentlicht worden sein.

Der Artikel beschreibt Dijkstras Vorschlag und beschreibt die Umstände, unter denen dieser gültig ist.Er führt aber auch eine Reihe von Gegenbeispielen (Probleme und Algorithmen) an, die mit strukturierten Schleifen allein nicht einfach reproduziert werden können.

Der Artikel enthält eine vollständige Problembeschreibung, den Verlauf, Beispiele und Gegenbeispiele.

Angezogen von Jay Ballou, der eine Antwort hinzufügt, füge ich meine 0,02 £ hinzu.Wenn Bruno Ranschaert dies nicht bereits getan hätte, hätte ich Knuths Artikel „Strukturierte Programmierung mit GOTO-Anweisungen“ erwähnt.

Eine Sache, die ich noch nicht erwähnt habe, ist die Art von Code, die zwar nicht gerade üblich ist, aber in Fortran-Lehrbüchern gelehrt wird.Dinge wie der erweiterte Bereich einer DO-Schleife und offen codierte Unterprogramme (denken Sie daran, dies wäre Fortran II oder Fortran IV oder Fortran 66 – nicht Fortran 77 oder 90).Es besteht zumindest die Möglichkeit, dass die syntaktischen Details ungenau sind, aber die Konzepte sollten genau genug sein.Die Snippets befinden sich jeweils innerhalb einer einzelnen Funktion.

Beachten Sie, dass das ausgezeichnete, aber veraltete (und vergriffene) Buch „Die Elemente des Programmierstils, 2. Aufl' von Kernighan & Plauger enthält einige reale Beispiele für den Missbrauch von GOTO aus Programmierlehrbüchern seiner Zeit (Ende der 70er Jahre).Das folgende Material stammt jedoch nicht aus diesem Buch.

Erweiterter Bereich für eine DO-Schleife

       do 10 i = 1,30
           ...blah...
           ...blah...
           if (k.gt.4) goto 37
91         ...blah...
           ...blah...
10     continue
       ...blah...
       return
37     ...some computation...
       goto 91

Ein Grund für solchen Unsinn war die gute alte Lochkarte.Möglicherweise fällt Ihnen auf, dass sich die Beschriftungen (schönerweise in der falschen Reihenfolge, da das kanonischer Stil war!) in Spalte 1 befinden (eigentlich mussten sie in den Spalten 1–5 sein) und der Code in den Spalten 7–72 steht (Spalte 6 war die Fortsetzung). Markierungsspalte).Die Spalten 73–80 erhielten eine Sequenznummer, und es gab Maschinen, die Lochkartenstapel in der Reihenfolge der Sequenznummern sortierten.Wenn Sie Ihr Programm auf sequenzierten Karten hätten und einige Karten (Zeilen) in die Mitte einer Schleife einfügen müssten, müssten Sie nach diesen zusätzlichen Zeilen alles neu stanzen.Wenn Sie jedoch eine Karte durch das GOTO-Zeug ersetzen, können Sie die Neusequenzierung aller Karten vermeiden – Sie stecken einfach die neuen Karten am Ende der Routine mit neuen Sequenznummern hinein.Betrachten Sie es als den ersten Versuch des „Green Computing“ – eine Einsparung von Lochkarten (oder genauer gesagt eine Einsparung von Abtipparbeit – und eine Einsparung von Folgefehlern bei der Neueingabe).

Oh, vielleicht ist Ihnen auch aufgefallen, dass ich schummele und nicht schreie – Fortran IV wurde normalerweise in Großbuchstaben geschrieben.

Offen codierte Unterroutine

       ...blah...
       i = 1
       goto 76
123    ...blah...
       ...blah...
       i = 2
       goto 76
79     ...blah...
       ...blah...
       goto 54
       ...blah...
12     continue
       return
76     ...calculate something...
       ...blah...
       goto (123, 79) i
54     ...more calculation...
       goto 12

Das GOTO zwischen den Labels 76 und 54 ist eine Version des berechneten Goto.Wenn die Variable i den Wert 1 hat, gehe zum ersten Label in der Liste (123);Wenn es den Wert 2 hat, gehe zum zweiten und so weiter.Das Fragment von 76 bis zum berechneten goto ist die offen codierte Unterroutine.Es handelte sich um einen Code, der eher wie eine Unterroutine ausgeführt, aber im Hauptteil einer Funktion geschrieben wurde.(Fortran hatte auch Anweisungsfunktionen – das waren eingebettete Funktionen, die in eine einzelne Zeile passten.)

Es gab schlechtere Konstrukte als das berechnete goto – Sie könnten Variablen Beschriftungen zuweisen und dann ein zugewiesenes goto verwenden.Googeln Gehe zu zugewiesen sagt mir, dass es aus Fortran 95 gelöscht wurde.Machen Sie sich ein Bild von der Revolution der strukturierten Programmierung, von der man durchaus sagen kann, dass sie mit Dijkstras „GOTO Considered Harmful“-Brief oder -Artikel öffentlich begonnen hat.

Ohne ein gewisses Wissen über die Art von Dingen, die in Fortran gemacht wurden (und in anderen Sprachen, von denen die meisten zu Recht auf der Strecke geblieben sind), ist es für uns Neulinge schwierig, die Tragweite des Problems zu verstehen, mit dem sich Dijkstra beschäftigte.Verdammt, ich habe erst zehn Jahre nach der Veröffentlichung dieses Briefes mit dem Programmieren begonnen (aber ich hatte das Pech, eine Zeit lang in Fortran IV zu programmieren).

Goto wurde als hilfreich erachtet.

Ich habe 1975 mit dem Programmieren begonnen.Für Programmierer der 1970er-Jahre sagten die Worte „wird als schädlich angesehen“ mehr oder weniger, dass neue Programmiersprachen mit modernen Kontrollstrukturen einen Versuch wert seien.Wir haben die neuen Sprachen ausprobiert.Wir haben schnell umgestellt.Wir sind nie zurückgegangen.

Wir sind nie dorthin zurückgekehrt, aber wenn Sie jünger sind, dann waren Sie noch nie dort.

Nun sind Kenntnisse in alten Programmiersprachen möglicherweise nicht sehr nützlich, außer als Indikator für das Alter des Programmierers.Jüngeren Programmierern fehlt jedoch dieser Hintergrund, sodass sie die Botschaft, die der Slogan „Goto gilt als schädlich“ vermittelt, nicht mehr verstehen seiner Zielgruppe zum Zeitpunkt seiner Einführung vorgestellt wurde.

Slogans, die man nicht versteht, sind nicht sehr aufschlussreich.Es ist wahrscheinlich am besten, solche Slogans zu vergessen.Solche Parolen helfen nicht.

Dieser besondere Slogan „Goto gilt als schädlich“ hat jedoch ein untotes Eigenleben angenommen.

Kann goto nicht missbraucht werden?Antwort:klar, aber na und?Praktisch jedes Programmierelement dürfen missbraucht werden.Die Bescheidenen bool zum Beispiel wird häufiger missbraucht, als manche von uns glauben möchten.

Im Gegensatz dazu kann ich mich nicht erinnern, seit 1990 einen einzigen tatsächlichen Fall von Goto-Missbrauch erlebt zu haben.

Das größte Problem bei goto ist wahrscheinlich nicht technischer, sondern sozialer Natur.Programmierer, die nicht viel wissen, scheinen manchmal das Gefühl zu haben, dass sie schlau klingen, wenn sie goto ablehnen.Möglicherweise müssen Sie solche Programmierer von Zeit zu Zeit zufriedenstellen.So ist das Leben.

Das Schlimmste an goto today ist, dass es nicht ausreichend genutzt wird.

So etwas gibt es nicht GOTO gilt als schädlich.

GOTO ist ein Werkzeug, und wie alle Werkzeuge kann es verwendet werden und missbraucht.

Es gibt jedoch viele Tools in der Programmierwelt, die dazu neigen missbraucht mehr als Sein gebraucht, und GOTO ist einer von ihnen.Die MIT Aussage von Delphi ist eine andere.

Persönlich verwende ich beides nicht in typischem Code, aber ich hatte die seltsame Verwendung von beidem GEHE ZU Und MIT Das war gerechtfertigt, und eine alternative Lösung hätte mehr Code enthalten.

Die beste Lösung wäre, dass der Compiler Sie einfach warnt, dass das Schlüsselwort vorhanden ist verdorben, und Sie müssten ein paar Pragma-Direktiven um die Anweisung stopfen, um die Warnungen zu entfernen.

Es ist, als würde man es seinen Kindern sagen nicht mit der Schere laufen.Scheren sind nicht schlecht, aber ihre Verwendung ist vielleicht nicht der beste Weg, um Ihre Gesundheit zu erhalten.

Seitdem ich angefangen habe, ein paar Dinge im Linux-Kernel zu tun, stören mich Gotos nicht mehr so ​​sehr wie früher.Zuerst war ich etwas entsetzt, als ich sah, dass sie (Kernel-Leute) Gotos in meinen Code eingefügt haben.Seitdem habe ich mich in einigen begrenzten Kontexten an die Verwendung von Gotos gewöhnt und werde sie jetzt gelegentlich selbst verwenden.Typischerweise handelt es sich dabei um einen Goto-Befehl, der zum Ende einer Funktion springt, um eine Art Bereinigung und Bailout durchzuführen, anstatt dieselbe Bereinigung und Bailout an mehreren Stellen in der Funktion zu duplizieren.Und normalerweise ist es nicht groß genug, um es an eine andere Funktion zu übergeben – z. B.Das Freigeben einiger lokal (k)mallocierter Variablen ist ein typischer Fall.

Ich habe Code geschrieben, der setjmp/longjmp nur einmal verwendet.Es war in einem MIDI-Drum-Sequenzer-Programm.Die Wiedergabe erfolgte in einem von der gesamten Benutzerinteraktion getrennten Prozess, und der Wiedergabeprozess nutzte den gemeinsam genutzten Speicher mit dem UI-Prozess, um die begrenzten Informationen zu erhalten, die für die Wiedergabe erforderlich waren.Wenn der Benutzer die Wiedergabe stoppen wollte, führte der Wiedergabevorgang einfach ein longjmp „zurück zum Anfang“ durch, um von vorne zu beginnen, und nicht ein kompliziertes Abwickeln der Stelle, an der es gerade ausgeführt wurde, als der Benutzer es stoppen wollte.Es hat großartig funktioniert, war einfach und ich hatte in diesem Fall nie irgendwelche Probleme oder Fehler.

setjmp/longjmp haben ihren Platz – aber diesen Ort werden Sie wahrscheinlich nur ab und zu besuchen.

Bearbeiten:Ich habe mir gerade den Code angesehen.Es war tatsächlich siglongjmp(), das ich verwendet habe, nicht longjmp (nicht, dass es eine große Sache wäre, aber ich hatte vergessen, dass siglongjmp überhaupt existierte.)

Solange man selbst denken konnte, war das nie der Fall.

Wenn Sie eine VM in C schreiben, stellt sich heraus, dass die Verwendung von (gccs) berechneten Gotos wie folgt aussieht:

char run(char *pc) {
    void *opcodes[3] = {&&op_inc, &&op_lda_direct, &&op_hlt};
    #define NEXT_INSTR(stride) goto *(opcodes[*(pc += stride)])
    NEXT_INSTR(0);
    op_inc:
    ++acc;
    NEXT_INSTR(1);
    op_lda_direct:
    acc = ram[++pc];
    NEXT_INSTR(1);
    op_hlt:
    return acc;
}

arbeitet viel schneller als der herkömmliche Switch innerhalb einer Schleife.

Weil goto kann zur verwirrenden Metaprogrammierung verwendet werden

Goto ist sowohl ein hohes Level und ein niedriges Niveau Steuerausdruck, und daher verfügt es einfach nicht über ein geeignetes Entwurfsmuster, das für die meisten Probleme geeignet ist.

Es ist niedriges Niveau in dem Sinne, dass ein goto eine primitive Operation ist, die etwas Höheres implementiert, z while oder foreach oder so.

Es ist hohes Level in dem Sinne, dass es, wenn es auf bestimmte Weise verwendet wird, Code, der in einer klaren Reihenfolge und ununterbrochen ausgeführt wird, mit Ausnahme strukturierter Schleifen, verwendet und ihn in ausreichend logische Teile umwandelt gotos, ein Schatz an Logik, der dynamisch neu zusammengesetzt wird.

Es gibt also eine prosaisch und ein teuflisch Seite zu goto.

Der prosaische Seite ist, dass ein nach oben zeigender goto eine vollkommen vernünftige Schleife implementieren kann und ein nach unten zeigender goto eine vollkommen vernünftige Schleife ausführen kann break oder return.Natürlich eine tatsächliche while, break, oder return wäre viel besser lesbar, da der arme Mensch die Wirkung des nicht simulieren müsste goto um den Überblick zu behalten.Also im Allgemeinen eine schlechte Idee.

Der böse Seite beinhaltet eine Routine, die goto nicht für while, break oder return verwendet, sondern für das, was aufgerufen wird Spaghetti-Logik.In diesem Fall konstruiert der Goto-freudige Entwickler Codeteile aus einem Labyrinth von Gotos, und die einzige Möglichkeit, ihn zu verstehen, besteht darin, ihn gedanklich als Ganzes zu simulieren, eine furchtbar ermüdende Aufgabe, wenn es viele Gotos gibt.Ich meine, stellen Sie sich die Mühe vor, Code auszuwerten, bei dem der else ist nicht genau eine Umkehrung von if, wo verschachtelt ifs könnte einige Dinge zulassen, die von außen abgelehnt wurden if, usw. usw.

Um das Thema wirklich abzudecken, sollten wir schließlich beachten, dass im Wesentlichen alle frühen Sprachen außer Algol zunächst nur einzelne Aussagen zum Gegenstand ihrer Versionen machten if-then-else.Die einzige Möglichkeit, einen bedingten Block durchzuführen, bestand also darin goto Umgehen Sie es mit einer umgekehrten Bedingung.Wahnsinn, ich weiß, aber ich habe einige alte Spezifikationen gelesen.Denken Sie daran, dass die ersten Computer in binärem Maschinencode programmiert wurden, daher vermute ich, dass jede Art von HLL ein Lebensretter war;Ich schätze, sie waren nicht allzu wählerisch, was die genauen HLL-Funktionen angeht.

Nachdem ich alles gesagt habe, habe ich immer einen aufgeklebt goto in jedes Programm, das ich geschrieben habe „nur um die Puristen zu ärgern“.

Programmierern die Verwendung der GOTO-Anweisung zu verweigern, ist so, als würde man einem Zimmermann sagen, er solle keinen Hammer verwenden, da dieser die Wand beschädigen könnte, während er einen Nagel einschlägt.Ein echter Programmierer weiß, wie und wann er ein GOTO verwenden muss.Ich habe einige dieser sogenannten „strukturierten Programme“ verfolgt. Nur um die Verwendung eines GOTO zu vermeiden, habe ich so schrecklichen Code gesehen, dass ich den Programmierer erschießen könnte.Ok, zur Verteidigung der anderen Seite: Ich habe auch echten Spaghetti-Code gesehen und wieder einmal: Diese Programmierer sollten auch erschossen werden.

Hier ist nur ein kleines Beispiel für den Code, den ich gefunden habe.

  YORN = ''
  LOOP
  UNTIL YORN = 'Y' OR YORN = 'N' DO
     CRT 'Is this correct? (Y/N) : ':
     INPUT YORN
  REPEAT
  IF YORN = 'N' THEN
     CRT 'Aborted!'
     STOP
  END

-----------------------ODER----------------------

10:  CRT 'Is this Correct (Y)es/(N)o ':

     INPUT YORN

     IF YORN='N' THEN
        CRT 'Aborted!'
        STOP
     ENDIF
     IF YORN<>'Y' THEN GOTO 10

„In diesem Link http://kerneltrap.org/node/553/2131"

Ironischerweise führte die Eliminierung von goto zu einem Fehler:Der Spinlock-Aufruf wurde weggelassen.

Das Originalpapier sollte als „Unconditional GOTO Considered Harmful“ betrachtet werden.Sie befürwortete insbesondere eine Form der Programmierung, die auf bedingten (if) und iterativ (while)-Konstrukte anstelle des bei frühem Code üblichen Test-and-Jump-Verfahren. goto ist in einigen Sprachen oder Situationen, in denen keine geeignete Kontrollstruktur vorhanden ist, immer noch nützlich.

So ziemlich der einzige Ort, dem ich zustimme. Gehe zu könnte wird verwendet, wenn Fehler behandelt werden müssen und jeder einzelne Fehlerpunkt eine spezielle Behandlung erfordert.

Wenn Sie beispielsweise Ressourcen abrufen und Semaphoren oder Mutexe verwenden, müssen Sie diese in der richtigen Reihenfolge abrufen und sollten sie immer in umgekehrter Reihenfolge freigeben.

Mancher Code erfordert ein sehr seltsames Muster zum Ergreifen dieser Ressourcen, und Sie können nicht einfach eine leicht zu wartende und verständliche Kontrollstruktur schreiben, um sowohl das Erfassen als auch das Freigeben dieser Ressourcen korrekt zu handhaben und so einen Deadlock zu vermeiden.

Es ist immer möglich, es ohne goto richtig zu machen, aber in diesem und einigen anderen Fällen ist Goto tatsächlich die bessere Lösung, vor allem aus Gründen der Lesbarkeit und Wartbarkeit.

-Adam

Eine moderne GOTO-Verwendung besteht darin, dass der C#-Compiler Zustandsmaschinen für durch yield return definierte Enumeratoren erstellt.

GOTO sollte von Compilern und nicht von Programmierern verwendet werden.

Bis C und C++ (neben anderen Übeltätern) „Breaks“ und „Fortsetzungen“ markiert haben, wird „goto“ weiterhin eine Rolle spielen.

Wenn GOTO selbst böse wäre, wären Compiler böse, weil sie JMPs generieren.Wenn das Springen in einen Codeblock, insbesondere das Folgen eines Zeigers, von Natur aus böse wäre, wäre die RETurn-Anweisung böse.Das Übel liegt vielmehr in der Möglichkeit des Missbrauchs.

Manchmal musste ich Apps schreiben, die eine Reihe von Objekten verfolgen mussten, wobei jedes Objekt als Reaktion auf Ereignisse einer komplizierten Abfolge von Zuständen folgen musste, aber das Ganze war definitiv Single-Thread.Eine typische Folge von Zuständen wäre, wenn sie in Pseudocode dargestellt würde:

request something
wait for it to be done
while some condition
    request something
    wait for it
    if one response
        while another condition
            request something
            wait for it
            do something
        endwhile
        request one more thing
        wait for it
    else if some other response
        ... some other similar sequence ...
    ... etc, etc.
endwhile

Ich bin mir sicher, dass das nichts Neues ist, aber die Art und Weise, wie ich in C(++) damit umgegangen bin, bestand darin, einige Makros zu definieren:

#define WAIT(n) do{state=(n); enque(this); return; L##n:;}while(0)
#define DONE state = -1

#define DISPATCH0 if state < 0) return;
#define DISPATCH1 if(state==1) goto L1; DISPATCH0
#define DISPATCH2 if(state==2) goto L2; DISPATCH1
#define DISPATCH3 if(state==3) goto L3; DISPATCH2
#define DISPATCH4 if(state==4) goto L4; DISPATCH3
... as needed ...

Dann (unter der Annahme, dass der Zustand anfänglich 0 ist) verwandelt sich die obige strukturierte Zustandsmaschine in den strukturierten Code:

{
    DISPATCH4; // or as high a number as needed
    request something;
    WAIT(1); // each WAIT has a different number
    while (some condition){
        request something;
        WAIT(2);
        if (one response){
            while (another condition){
                request something;
                WAIT(3);
                do something;
            }
            request one more thing;
            WAIT(4);
        }
        else if (some other response){
            ... some other similar sequence ...
        }
        ... etc, etc.
    }
    DONE;
}

In einer Variation davon kann es CALL und RETURN geben, sodass einige Zustandsmaschinen wie Unterprogramme anderer Zustandsmaschinen fungieren können.

Ist es ungewöhnlich?Ja.Ist seitens des Betreuers etwas Lernerfahrung erforderlich?Ja.Zahlt sich dieses Lernen aus?Ich glaube schon.Könnte es ohne GOTOs gemacht werden, die in Blöcke springen?Nein.

Ich vermeide es, da ein Kollege/Manager seine Verwendung zweifellos in Frage stellen wird, sei es bei einer Codeüberprüfung oder wenn er darauf stößt.Obwohl ich denke, dass es einen Nutzen hat (z. B. im Fall der Fehlerbehandlung), werden Sie mit einem anderen Entwickler in Konflikt geraten, der irgendein Problem damit haben wird.

Es lohnt sich nicht.

Ich sah mich tatsächlich gezwungen, ein goto zu verwenden, weil ich mir buchstäblich keinen besseren (schnelleren) Weg vorstellen konnte, diesen Code zu schreiben:

Ich hatte ein komplexes Objekt und musste eine Operation daran durchführen.Wenn sich das Objekt in einem Zustand befände, könnte ich eine schnelle Version des Vorgangs ausführen, andernfalls müsste ich eine langsame Version des Vorgangs ausführen.Die Sache war, dass man in manchen Fällen mitten im langsamen Betrieb erkennen konnte, dass dies mit dem schnellen Betrieb möglich gewesen wäre.

SomeObject someObject;    

if (someObject.IsComplex())    // this test is trivial
{
    // begin slow calculations here
    if (result of calculations)
    {
        // just discovered that I could use the fast calculation !
        goto Fast_Calculations;
    }
    // do the rest of the slow calculations here
    return;
}

if (someObject.IsmediumComplex())    // this test is slightly less trivial
{
    Fast_Calculations:
    // Do fast calculations
    return;
}

// object is simple, no calculations needed.

Dies befand sich in einem geschwindigkeitskritischen Teil des Echtzeit-UI-Codes, daher denke ich ehrlich, dass ein GOTO hier gerechtfertigt war.

Hugo

In fast allen Situationen, in denen ein goto verwendet werden kann, können Sie dasselbe mit anderen Konstrukten tun.Goto wird vom Compiler ohnehin verwendet.

Ich persönlich verwende es nie explizit und muss es auch nie tun.

Eines habe ich noch nicht gesehen beliebig Eine der Antworten hier ist, dass es oft eine „Gehe zu“-Lösung gibt effizienter als eine der oft genannten strukturierten Programmierlösungen.

Betrachten Sie den Fall mit vielen verschachtelten Schleifen, bei dem „goto“ anstelle einer Reihe von verwendet wird if(breakVariable) Abschnitte sind offensichtlich effizienter.Die Lösung „Fügen Sie Ihre Schleifen in eine Funktion ein und verwenden Sie Return“ ist oft völlig unvernünftig.Für den wahrscheinlichen Fall, dass die Schleifen lokale Variablen verwenden, müssen Sie sie jetzt alle durch Funktionsparameter leiten, was möglicherweise eine Menge zusätzlicher Probleme verursacht, die dadurch entstehen.

Betrachten Sie nun den Bereinigungsfall, den ich selbst ziemlich oft verwendet habe und der so häufig vorkommt, dass er vermutlich für die Struktur try{} Catch {} verantwortlich ist, die in vielen Sprachen nicht verfügbar ist.Die Anzahl der Prüfungen und zusätzlichen Variablen, die erforderlich sind, um dasselbe zu erreichen, ist weitaus schlimmer als die ein oder zwei Anweisungen, um den Sprung durchzuführen, und auch hier ist die Lösung der zusätzlichen Funktion überhaupt keine Lösung.Man kann mir nicht sagen, dass das einfacher zu handhaben oder besser lesbar ist.

Nun sind Codespeicherplatz, Stapelnutzung und Ausführungszeit für viele Programmierer in vielen Situationen vielleicht nicht mehr wichtig genug, aber wenn Sie sich in einer eingebetteten Umgebung mit nur 2 KB Codespeicherplatz zum Arbeiten befinden, sind 50 Bytes zusätzlicher Anweisungen erforderlich, um eine klare Definition zu vermeiden „goto“ ist einfach lächerlich, und das kommt nicht so selten vor, wie viele erfahrene Programmierer glauben.

Die Aussage, dass „goto schädlich ist“, war sehr hilfreich auf dem Weg zur strukturierten Programmierung, auch wenn es immer eine Überverallgemeinerung war.Zu diesem Zeitpunkt haben wir alle genug davon gehört, um vorsichtig zu sein, es zu verwenden (wie wir es sollten).Wenn es offensichtlich das richtige Werkzeug für den Job ist, brauchen wir keine Angst davor zu haben.

Sie können es verwenden, um eine tief verschachtelte Schleife zu durchbrechen, aber in den meisten Fällen kann Ihr Code so umgestaltet werden, dass er ohne tief verschachtelte Schleifen sauberer ist.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top