GOTO immer noch als schädlich angesehen? [geschlossen]

282

Jeder kennt Dijkstras Briefe an den Herausgeber: Gehen Sie zu einer Aussage, die als schädlich angesehen wird (auch hier .html-Transkript und hier .pdf), und seitdem gab es einen gewaltigen Druck, die goto-Aussage nach Möglichkeit zu vermeiden. Mit goto können Sie zwar nicht wartbaren, weitläufigen Code erzeugen, dieser bleibt jedoch in modernen Programmiersprachen erhalten . Sogar die erweiterte Struktur zur Fortsetzung der Steuerung in Schema kann als hochentwickeltes Goto beschrieben werden.

Welche Umstände rechtfertigen die Verwendung von goto? Wann ist es am besten zu vermeiden?

Als Folgefrage: C bietet ein Paar von Funktionen, setjmp und longjmp, die die Möglichkeit bieten, nicht nur innerhalb des aktuellen Stapelrahmens, sondern innerhalb eines der aufrufenden Rahmen zu gelangen. Sollten diese als so gefährlich wie goto angesehen werden? 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. 1968 veröffentlichten die Mitteilungen der ACM einen meiner Texte unter dem Titel " Die goto-Erklärung als schädlich ", auf den in späteren Jahren am häufigsten Bezug genommen wurde, leider jedoch häufig von Autoren, die nicht mehr davon gesehen hatten als seine Titel, der zu einem Eckpfeiler meines Ruhms wurde, indem er zu einer Vorlage wurde: Wir würden alle Arten von Artikeln unter dem Titel "X als schädlich angesehen" für fast jedes X sehen, einschließlich eines mit dem Titel "Dijkstra als schädlich". Aber was war passiert? Ich hatte ein Papier unter dem Titel " Ein Fall gegen die goto-Erklärung " eingereicht", der, um seine Veröffentlichung zu beschleunigen, in einen" Brief an den Herausgeber "umgewandelt worden war und ihm dabei einen neuen Titel seiner eigenen Erfindung gegeben hatte! Der Herausgeber war Niklaus Wirth.

Ein gut durchdachtes klassisches Papier zu diesem Thema, das mit dem von Dijkstra übereinstimmt, ist Structured Programming with go to Statements von Donald E. Knuth. Das Lesen hilft sowohl, den Kontext wiederherzustellen, als auch ein nicht dogmatisches Verständnis des Themas. In diesem Artikel wird die Meinung von Dijkstra zu diesem Fall berichtet und ist noch stärker:

Donald E. Knuth: Ich glaube, dass ich durch die Darstellung einer solchen Ansicht den Ideen von Dijkstra nicht scharf widerspreche, da er kürzlich Folgendes geschrieben hat: "Bitte geraten Sie nicht in die Falle, zu glauben, dass ich schrecklich dogmatisch gegenüber [dem gehe zu Aussage]. Ich habe das unangenehme Gefühl, dass andere eine Religion daraus machen, als ob die konzeptuellen Probleme der Programmierung durch einen einzigen Trick, durch eine einfache Form der Codierungsdisziplin gelöst werden könnten! "

MaD70
quelle
1
C # 's goto ist nicht dasselbe wie das goto, über das Dijkstra gesprochen hat, genau aus den Gründen, über die Dijkstra gesprochen hat. Das ist sowohl schädlich als damals, aber auch viel weniger notwendig, weil moderne Sprachen alternative Kontrollstrukturen bieten. C # goto ist extrem eingeschränkt.
Jalf
25
Gotos sind gut, wenn sie Klarheit schaffen. Wenn Sie eine lange verschachtelte Schleife haben, kann es besser sein, sie zu verlassen, als "break" -Variablen zu setzen und zu brechen, bis Sie herauskommen.
Simendsjo
28
Wenn Sie eine verschachtelte Schleife in 4 Tiefen haben (nicht, dass es eine gute Sache ist), müssen Sie temporäre Werte festlegen, um aus allen auszubrechen. Ein Goto hier ist mir viel klarer und die IDE sollte leicht zeigen, wo das Goto ist. Das heißt, die Verwendung von goto sollte spärlich sein, und meiner Meinung nach nur nach unten gehen, um Code zu überspringen
simendsjo
7
Ich schlage vor, Sie lesen die neuntausendundein getaggten Threads goto.
Matti Virkkunen
5
Es gibt eine Sache, die eindeutig schlimmer ist als die Verwendung goto: Hacken strukturierter Programmierwerkzeuge zusammen, um a zu implementieren goto.

Antworten:

184

Die folgenden Aussagen sind Verallgemeinerungen; Während es immer möglich ist, eine Ausnahme zu plädieren, ist es normalerweise (meiner Erfahrung und bescheidenen Meinung nach) das Risiko nicht wert.

  1. Die uneingeschränkte Verwendung von Speicheradressen (entweder GOTO oder Rohzeiger) bietet zu viele Möglichkeiten, um leicht vermeidbare Fehler zu machen.
  2. Je mehr Möglichkeiten es gibt, an einen bestimmten "Ort" im Code zu gelangen, desto weniger sicher kann man sein, wie der Zustand des Systems zu diesem Zeitpunkt ist. (Siehe unten.)
  3. Strukturierte Programmierung IMHO geht es weniger darum, "GOTOs zu vermeiden", als vielmehr darum, die Struktur des Codes an die Struktur der Daten anzupassen. Beispielsweise wird eine sich wiederholende Datenstruktur (z. B. Array, sequentielle Datei usw.) natürlich von einer wiederholten Codeeinheit verarbeitet. Durch integrierte Strukturen (z. B. while, for, till, for-each usw.) kann der Programmierer die Mühe vermeiden, dieselben klischeehaften Codemuster zu wiederholen.
  4. Selbst wenn es sich bei GOTO um ein Implementierungsdetail auf niedriger Ebene handelt (was nicht immer der Fall ist!), Liegt es unter dem Niveau, an das der Programmierer denken sollte. Wie viele Programmierer gleichen ihre persönlichen Scheckbücher in rohen Binärdateien aus? Wie viele Programmierer machen sich Sorgen darüber, welcher Sektor auf der Festplatte einen bestimmten Datensatz enthält, anstatt nur einen Schlüssel für eine Datenbank-Engine bereitzustellen (und auf wie viele Arten könnte etwas schief gehen, wenn wir wirklich alle unsere Programme in Bezug auf physische Festplattensektoren geschrieben haben)?

Fußnoten zu den oben genannten:

Beachten Sie in Bezug auf Punkt 2 den folgenden Code:

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

An der Stelle "etwas tun" im Code können wir mit hoher Sicherheit angeben, dass agrößer als ist b. (Ja, ich ignoriere die Möglichkeit eines nicht eingefangenen Ganzzahlüberlaufs. Lassen Sie uns kein einfaches Beispiel festhalten.)

Auf der anderen Seite, wenn der Code so gelesen hätte:

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

Die Vielzahl der Möglichkeiten, um zu Label 10 zu gelangen, bedeutet, dass wir viel härter arbeiten müssen, um uns über die Beziehungen zwischen aund bzu diesem Zeitpunkt sicher zu sein . (In der Tat ist es im allgemeinen Fall unentscheidbar!)

In Bezug auf Punkt 4 ist der gesamte Begriff "irgendwohin gehen" im Code nur eine Metapher. Nichts "geht" wirklich irgendwo in der CPU außer Elektronen und Photonen (für die Abwärme). Manchmal geben wir eine Metapher für eine andere, nützlichere auf. Ich erinnere mich, dass ich (vor einigen 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 parametrlose Routinen außerhalb der Zeile kompiliert wurden und dann ein einzelner VM-Opcode mit zwei Argumenten verwendet wurde, der den booleschen Wert der Bedingung zum Aufrufen der einen oder anderen verwendete. Das Konzept war einfach "wählen, was jetzt aufgerufen werden soll" anstatt "hierher oder dorthin gehen". Wieder nur eine Änderung der Metapher.

joel.neely
quelle
2
Ein guter Punkt. In höheren Sprachen bedeutet goto eigentlich gar nichts (erwägen Sie, zwischen Methoden in Java zu wechseln). Eine Haskell-Funktion kann aus einem einzelnen Ausdruck bestehen. versuche mit einem goto rauszuspringen!
Mechanische Schnecke
2
Postscript funktioniert wie Ihr Punkt 4 Beispiel.
Luser Droog
Smalltalk funktioniert ähnlich wie das Beispiel in Punkt 4, wenn Sie mit "ähnlich" "nichts wie prozedurale Sprachen" meinen. : P Es gibt keine Flusskontrolle in der Sprache; Alle Entscheidungen werden über Polymorphismus getroffen ( trueund falsesind von unterschiedlicher Art), und jeder Zweig eines if / else ist im Grunde ein Lambda.
CHao
Dies sind gültige Punkte, aber am Ende wiederholen sie nur, wie schlecht goto sein kann, "wenn es missbraucht wird". Break, Continue, Exit, Return, Gosub, Settimeout, Global, Include usw. sind alles moderne Techniken, die eine mentale Verfolgung des Flusses von Dingen erfordern und alle missbraucht werden können, um Spaghetti-Code zu erstellen und herumzuspringen, um Unsicherheit über variable Zustände zu erzeugen. Um fair zu sein, obwohl ich noch nie eine schlechte Verwendung von goto aus erster Hand gesehen habe, habe ich auch immer nur ein- oder zweimal gesehen, wie es verwendet wurde. Das spricht für die Aussage, dass es meistens immer bessere Dinge zu verwenden gibt.
Beejor
gotoin einer modernen Programmiersprache (Go) stackoverflow.com/a/11065563/3309046 .
Nishanths
245

XKCDs GOTO Comic

Ein Mitarbeiter von mir sagte, der einzige Grund, ein GOTO zu verwenden, sei, wenn Sie sich so weit in eine Ecke programmiert haben, dass dies der einzige Ausweg ist. Mit anderen Worten, richtiges Design im Voraus und Sie müssen später kein GOTO mehr verwenden.

Ich dachte, dieser Comic zeigt auf wundervolle Weise: "Ich könnte den Programmfluss umstrukturieren oder stattdessen ein kleines 'GOTO' verwenden." Ein GOTO ist ein schwacher Ausweg, wenn Sie ein schwaches Design haben. Velociraptors jagen den Schwachen nach .

Jim McKeeth
quelle
41
GOTO kann von einem beliebigen Punkt zu einem anderen springen. Velociraptor sprang aus dem Nichts hierher!
Rpattabi
29
Ich finde den Witz überhaupt nicht lustig, weil jeder weiß, dass man auch verlinken muss, bevor man ihn ausführen kann.
Kinjal Dixit
31
Ihr Mitarbeiter liegt falsch und hat offensichtlich Knuths Zeitung nicht gelesen.
Jim Balter
27
Ich habe im Laufe der Jahre kein Ende von verschleiertem und ansonsten verzerrtem Code gesehen, nur um einen Fehler zu vermeiden. @jimmcKeeth, Der obige xkcd-Cartoon stellt nicht fest, dass goto schwach ist. Es macht sich über die Hysterie lustig, die damit verbunden ist.
4
Sie erkennen, dass der Quellcode der Delphi-Bibliothek goto-Anweisungen enthält. Und dies sind angemessene Verwendungen von goto.
David Heffernan
131

Manchmal ist es gültig, 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.

Rob Walker
quelle
29
Ich stimme zu, es gibt legitime Anwendungsfälle, in denen goto Code vereinfachen und lesbarer / wartbarer machen kann, aber es scheint eine Art goto-Phobie zu geben ...
Pop Catalin
48
@ Bob: Es ist schwierig, den err_cleanup-Code in eine Unterroutine zu verschieben, wenn lokale Variablen bereinigt werden.
Niki
4
Eigentlich habe ich es in COM / VB6 verwendet, nur weil ich keine Alternative hatte, nicht weil es eine Alternative war. Wie glücklich ich heutzutage mit try / catch / finally bin.
Rui Craveiro
10
@ user4891 Der idiomatische C ++ - Weg ist nicht try {} catch () {cleanup; }, sondern RAII, wo Ressourcen, die bereinigt werden müssen, in Destruktoren ausgeführt werden. Jeder Konstruktor / Destruktor verwaltet genau eine Ressource.
David Stone
5
Es gibt zwei Möglichkeiten, dies in C ohne goto zu schreiben. und beide sind viel kürzer. Entweder: if (f ()) if (g ()) if (h ()) gibt Erfolg zurück; Aufräumen(); Rückgabefehler; oder: wenn (f () && g () && h ()) Erfolg zurückgibt; Aufräumen(); Rückgabefehler;
LHP
124

Ich kann mich nur daran erinnern, ein goto einmal benutzt zu haben. Ich hatte eine Reihe von fünf verschachtelten gezählten Schleifen und musste unter bestimmten Bedingungen frühzeitig von innen aus der gesamten Struktur ausbrechen können:

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

ENDOFLOOPS:

Ich hätte einfach eine boolesche Unterbrechungsvariable deklarieren und 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 ist.

Keine Velociraptoren haben mich angegriffen.

shsteimer
quelle
83
"Refactor es in eine Funktion und ersetze goto durch return :)", und der Unterschied ist? Was ist wirklich der Unterschied? ist nicht auch eine Rückkehr zu gehen? Returns bremst auch den strukturierten Fluss wie goto, und in diesem Fall tun sie es auf die gleiche Weise (auch wenn goto für gemeinere Dinge verwendet werden kann)
Pop Catalin
38
Das Verschachteln vieler Schleifen ist normalerweise ein ganz eigener Codegeruch. Wenn Sie keine 5-dimensionale Array-Multiplikation durchführen, ist es schwierig, sich eine Situation vorzustellen, in der einige der inneren Schleifen nicht sinnvoll in kleinere Funktionen extrahiert werden konnten. Wie bei allen Faustregeln gibt es wohl eine Handvoll Ausnahmen.
Doug McClean
5
Das Ersetzen durch eine Rückgabe funktioniert nur, wenn Sie eine Sprache verwenden, die Rückgaben unterstützt.
Loren Pechtel
31
@leppie: Die Generation, die sich gegen gotouns auflehnte und uns strukturierte Programme gab, lehnte aus demselben Grund auch frühe Renditen ab. Es kommt darauf an, wie lesbar der Code ist, wie deutlich er die Absicht des Programmierers ausdrückt. Das Erstellen einer Funktion für keinen anderen Zweck als die Vermeidung der Verwendung eines bösartigen Schlüsselworts führt zu einem schlechten Zusammenhalt: Die Heilung ist schlechter als die Krankheit.
Schlamm
21
@ ButtleButkus: Ehrlich gesagt ist das genauso schlimm, wenn nicht schlimmer. Zumindest mit a gotokann man das Ziel explizit angeben. Mit break 5;(1) muss ich Schleifenschließungen zählen, um das Ziel zu finden; und (2) wenn sich die Schleifenstruktur jemals ändert, kann es durchaus erforderlich sein, diese Nummer zu ändern, um das Ziel korrekt zu halten. Wenn ich es vermeiden will, gotosollte der Gewinn darin liegen, dass ich solche Dinge nicht manuell verfolgen muss.
CHao
93

Wir hatten diese Diskussion bereits und ich stehe zu meinem Standpunkt .

Darüber hinaus bin ich mit Menschen zu beschreiben geordnete Sprachstrukturen als „satt gotoin der Verkleidung“ , weil sie eindeutig nicht den Punkt bekam überhaupt . Zum Beispiel:

Sogar die erweiterte Struktur der Fortsetzungskontrolle in Schema kann als hochentwickeltes Goto beschrieben werden.

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

In ähnlicher Weise ist die Aussage „GOTO ist ein Werkzeug, und wie alle Werkzeuge kann es verwendet und missbraucht werden“ völlig falsch. Kein moderner Bauarbeiter würde einen Stein benutzen und behaupten, er sei „ein Werkzeug“. Steine ​​wurden durch Hämmer ersetzt. gotowurde durch Kontrollstrukturen ersetzt. Wenn der Bauarbeiter ohne Hammer in freier Wildbahn gestrandet wäre, würde er natürlich stattdessen einen Stein verwenden. Wenn eine Programmiererin eine minderwertige Programmiersprache verwenden muss, die nicht über Feature X verfügt, muss sie möglicherweise gotostattdessen eine Programmiersprache verwenden . Aber wenn sie es irgendwo anders anstelle der entsprechenden Sprachfunktion verwendet, hat sie die Sprache offensichtlich nicht richtig verstanden und verwendet sie falsch. So einfach ist das wirklich.

Konrad Rudolph
quelle
82
Natürlich ist die richtige Verwendung eines Felsens kein Hammer. Eine der richtigen Anwendungen ist ein Schleifstein oder das Schärfen anderer Werkzeuge. Sogar das niedrige Gestein ist bei richtiger Verwendung ein gutes Werkzeug. Sie müssen nur die richtige Verwendung finden. Gleiches gilt für goto.
Kibbee
10
Was ist die richtige Verwendung von Goto? Für jeden erdenklichen Fall gibt es ein anderes Werkzeug, das besser geeignet ist. Und selbst wird Ihr Schleifstein tatsächlich von High-Tech-Werkzeugen heute ersetzt, auch wenn sie nach wie vor bestand aus Gestein. Es gibt einen großen Unterschied zwischen einem Rohstoff und einem Werkzeug.
Konrad Rudolph
8
@jalf: Goto sicherlich tut exist in C #. Siehe stackoverflow.com/questions/359436/…
Jon Skeet
51
Ich bin bestürzt, dass so viele Leute diesen Beitrag gutheißen. Ihr Beitrag schien nur effektiv zu sein, weil Sie sich nie die Mühe gemacht haben, zu hinterfragen, welche Logik Sie tatsächlich ausgeführt haben, und Sie Ihren Irrtum nicht bemerkt haben. Gestatten Sie mir, Ihren gesamten Beitrag zu paraphrasieren: "Es gibt in jeder Situation ein überlegenes Werkzeug für ein Goto, daher sollten Goto niemals verwendet werden." Dies ist eine logische Bedingung, und als solche wirft Ihr gesamter Beitrag im Wesentlichen die Frage auf: "Woher wissen Sie, dass es in jeder Situation ein überlegenes Werkzeug für einen Goto gibt?"
Codierung mit Stil
10
@Coding: Nein, Sie haben den Kern des Beitrags völlig übersehen. Es war eher eine Gegenrede als ein isoliertes, vollständiges Argument. Ich habe lediglich auf den Irrtum im Hauptargument „für“ hingewiesen goto. Sie haben insofern Recht, als ich gotoper se kein Argument dagegen vorbringe - ich hatte nicht vor -, also gibt es keine Fragen.
Konrad Rudolph
91

Goto steht auf meiner Liste der Dinge, die nur deswegen in ein Programm aufgenommen werden sollen, ganz unten. Das heißt nicht, dass es inakzeptabel ist.

Gehe zu kann für Zustandsautomaten schön sein. Eine switch-Anweisung in einer Schleife ist (in der Reihenfolge ihrer typischen Bedeutung): (a) nicht repräsentativ für den Kontrollfluss, (b) hässlich, (c) je nach Sprache und Compiler möglicherweise ineffizient. Am Ende schreiben Sie eine Funktion pro Status und führen Dinge wie "return NEXT_STATE;" die sehen sogar aus wie goto.

Zugegeben, es ist schwierig, Zustandsautomaten so zu codieren, dass sie leicht verständlich sind. Keine dieser Schwierigkeiten hat jedoch mit der Verwendung von goto zu tun, und keine davon kann durch die Verwendung alternativer Kontrollstrukturen verringert werden. Es sei denn, Ihre Sprache verfügt über ein Konstrukt "Zustandsmaschine". Meins nicht.

In den seltenen Fällen, in denen Ihr Algorithmus in Bezug auf einen Pfad durch eine Folge von Knoten (Zuständen), die durch eine begrenzte Anzahl zulässiger Übergänge (gotos) verbunden sind, am verständlichsten ist, 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. Obwohl nicht allgemein gelobt, werden Ausnahmen im Allgemeinen als "gültige" Kontrollstruktur angesehen.

setjmp / longjmp sind "gefährlicher" als goto in dem Sinne, dass sie schwerer richtig zu verwenden sind, egal verständlich.

Es gab und wird nie eine Sprache geben, in der es am wenigsten schwierig ist, schlechten Code zu schreiben. - Donald Knuth.

Das Entfernen 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, dass C in der Lage sein soll , als verherrlichte Assembler-Sprache zu fungieren.

Als nächstes wird es "Zeiger als schädlich" sein, dann "Ententypisierung als schädlich". Wer bleibt dann übrig, um Sie zu verteidigen, wenn er Ihr unsicheres Programmierkonstrukt wegnimmt? Eh?

Steve Jessop
quelle
14
Persönlich ist dies der Kommentar, dem ich den Scheck gegeben hätte. Eine Sache, auf die ich die Leser hinweisen möchte, ist, dass der esoterische Begriff "Zustandsmaschinen" alltägliche Dinge wie lexikalische Analysatoren umfasst. Überprüfen Sie die Ausgabe von Lex irgendwann. Voller Gotos.
TED
2
Sie können eine switch-Anweisung innerhalb einer Schleife (oder eines Ereignishandlers) verwenden, um Zustandsmaschinen einwandfrei auszuführen. Ich habe viele Zustandsautomaten gemacht, ohne jemals einen jmp oder goto verwenden zu müssen.
Scott Whitlock
11
+1 Diese Pfeile auf Zustandsautomaten sind näher als jede andere Kontrollstruktur 'goto' zugeordnet. Sicher, Sie können einen Schalter innerhalb einer Schleife verwenden - genau wie Sie eine Reihe von Gotos anstelle einer Weile für andere Probleme verwenden können, aber es ist normalerweise eine Idee; Das ist der springende Punkt dieser Diskussion.
Edmund
2
Kann ich Sie zu diesem letzten Absatz zitieren?
Chris Lutz
2
Und hier im Jahr 2013 haben wir bereits die Phase "Zeiger als schädlich angesehen" erreicht (und sind irgendwie darüber hinweggekommen).
Isiah Meadows
68

Unter Linux: Verwenden von goto In Kernel Code auf Kernel Trap gibt es eine Diskussion mit Linus Torvalds und einem "neuen Mann" über die Verwendung von GOTOs in Linux-Code. Es gibt einige sehr gute Punkte und Linus hat diese übliche Arroganz angezogen :)

Einige Passagen:

Linus: Nein, Sie wurden von CS-Leuten einer Gehirnwäsche unterzogen, die dachten, dass Niklaus Wirth tatsächlich wusste, wovon er sprach. Er tat es nicht. Er hat keine verdammte Ahnung.

- -

Linus: Ich denke, gotos sind in Ordnung und oft besser lesbar als große Einrückungen.

- -

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

Marcio Aguiar
quelle
9
Das ist ein guter Punkt, wie? Sie diskutieren seine Verwendung in einer Sprache, die nichts anderes hat. Wenn Sie die Programmierung in Assembler, alle Zweige und Sprünge sind GOTOs. Und C ist und war eine "tragbare Assemblersprache". Darüber hinaus sagen die Passagen, die Sie zitieren, nichts darüber aus, warum er goto für gut hält.
Jalf
5
Beeindruckend. Das ist enttäuschend zu lesen. Man könnte meinen, ein großer OS-Typ wie Linus Torvalds würde es besser wissen, als das zu sagen. Pascal (Old-School-Pascal, nicht die moderne Object-Version) war das, in dem Mac OS Classic in der Zeit von 68.000 geschrieben wurde, und es war das fortschrittlichste Betriebssystem seiner Zeit.
Mason Wheeler
6
@mason Classic Mac OS hatte einige Pascal-Bibliotheken (schließlich - die Pascal-Laufzeit beanspruchte in den frühen Macs zu viel Speicher), aber der Großteil des Kerncodes wurde in Assembler geschrieben, insbesondere die Grafik- und UI-Routinen.
Jim Dovey
30
Linus argumentiert nur (explizit wie Rik van Riel in dieser Diskussion) für goto für die Behandlung des Exit-Status, und er tut dies auf der Grundlage der Komplexität, die Cs alternative Konstrukte mit sich bringen würden, wenn sie stattdessen verwendet würden.
Charles Stewart
21
IMHO Linus hat in dieser Frage Recht. Sein Punkt ist, dass in C geschriebener Kernel-Code, der etwas Ähnliches wie die Ausnahmebehandlung implementieren muss, am klarsten und einfachsten mit einem goto geschrieben wird. Das Idiom goto cleanup_and_exitist eine der wenigen „gut“ Verwendungen gehe links jetzt, wo wir haben for, whileund ifunser Steuerfluss zu verwalten. Siehe auch: programmers.stackexchange.com/a/154980
steveha
50

Funktioniert in C gotonur im Rahmen der aktuellen Funktion, wodurch potenzielle Fehler lokalisiert werden. setjmpund longjmpsind weitaus gefährlicher, nicht lokal, kompliziert und implementierungsabhängig. In der Praxis sind sie jedoch zu dunkel und ungewöhnlich, um viele Probleme zu verursachen.

Ich glaube, dass die Gefahr von gotoin C stark übertrieben ist. Denken Sie daran, dass die ursprünglichen gotoArgumente in den Tagen von Sprachen wie altmodischem BASIC stattfanden, in denen Anfänger Spaghetti-Code wie folgt schrieben:

3420 IF A > 2 THEN GOTO 1430

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

smh
quelle
7
Als BASIC zum ersten Mal verfügbar war, gab es keine Alternative zu GOTO nnnn und GOSUB mmmm, um herumzuspringen. Strukturierte Konstrukte wurden später hinzugefügt.
Jonathan Leffler
7
Sie verpassen den Punkt ... selbst dann mussten Sie keine Spaghetti schreiben ... Ihre GOTOs konnten immer diszipliniert verwendet werden
JoelFan
Es ist auch erwähnenswert, dass das Verhalten von setjmp/ longjmpnur angegeben wurde, wenn sie als Mittel zum Springen zu einem Punkt innerhalb eines Bereichs von anderen Orten innerhalb desselben Bereichs verwendet wurden. Sobald das Steuerelement den Bereich verlässt, in dem setjmpes ausgeführt wird, führt jeder Versuch, die longjmpvon erstellte Struktur zu verwenden, zu einem setjmpundefinierten Verhalten.
Superkatze
9
Bei einigen Versionen von BASIC können Sie dies tun GOTO A * 40 + B * 200 + 30. Es ist nicht schwer zu sehen, wie praktisch und sehr gefährlich dies war.
Jon Hanna
1
@Hjulle würde den Ausdruck berechnen und dann zur Codezeile mit dieser Nummer gehen (explizite Zeilennummern waren eine Anforderung der meisten früheren Dialekte). ZX Spectrum Basic war eine, die das akzeptieren würde
Jon Hanna
48

Heutzutage ist die große Sache mit der GOTOAussage schwer zu erkennen, da die Leute mit "strukturierter Programmierung" die Debatte meistens gewonnen haben und die heutigen Sprachen über ausreichende Kontrollflussstrukturen verfügen, um dies zu vermeiden GOTO.

Zählen Sie die Anzahl der gotos in einem modernen C-Programm. Fügen Sie nun die Anzahl der break, continueund returnAussagen. Darüber hinaus fügen Sie die Anzahl der Male , die Sie verwenden if, else, while, switchoder case. Das ist ungefähr, wie viele GOTOs Ihr Programm gehabt hätte, wenn Sie 1968 in FORTRAN oder BASIC geschrieben hätten, als Dijkstra seinen Brief schrieb.

Zu dieser Zeit fehlten die Programmiersprachen im Kontrollfluss. Zum Beispiel im Original Dartmouth BASIC:

  • IFAussagen hatten keine ELSE. Wenn Sie einen 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 Ihre IFAussage keine benötigte ELSE, war sie dennoch auf eine einzelne Zeile beschränkt, die normalerweise aus einer bestand GOTO.

  • Es gab keine DO...LOOPAussage. Für Nicht- FORSchleifen mussten Sie die Schleife mit einem expliziten GOTOoder IF...GOTOzurück zum Anfang beenden .

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

So endete Sie mit einer bis Menge von GOTOs in Ihrem Programm. Und Sie konnten sich nicht auf die Beschränkung von GOTOs auf eine einzelne Subroutine verlassen (weil dies GOSUB...RETURNein so schwaches Konzept von Subroutinen war), so dass diese GOTOs überall hingehen konnten . Dies machte es offensichtlich schwierig, dem Kontrollfluss zu folgen.

Hierher kam die Anti- GOTOBewegung.

dan04
quelle
Eine andere zu beachtende Sache ist, dass die bevorzugte Art, Code zu schreiben, wenn man einen Code in einer Schleife hätte, der selten ausgeführt werden müsste, 420 if (rare_condition) then 3000// 430 and onward: rest of loop and other main-line code// 3000 [code for rare condition]// wäre 3230 goto 430. Das Schreiben von Code auf diese Weise vermeidet genommene Verzweigungen oder Sprünge im allgemeinen Hauptzeilenfall, macht es jedoch schwierig, den Dingen zu folgen. Die Vermeidung von Verzweigungen im Assembler-Code kann schlechter sein, wenn einige Verzweigungen auf z. B. +/- 128 Byte beschränkt sind und manchmal keine komplementären Paare haben (z. B. "cjne" existiert, aber nicht "cje").
Supercat
Ich habe einmal einen Code für das 8x51 geschrieben, der einen Interrupt hatte, der alle 128 Zyklen einmal ausgeführt wurde. Jeder zusätzliche Zyklus, der in dem allgemeinen Fall dieses ISR verbracht wird, würde die Ausführungsgeschwindigkeit des Hauptzeilencodes um mehr als 1% verringern (ich denke, 90 von 128 Zyklen standen normalerweise der Hauptleitung zur Verfügung), und jeder Verzweigungsbefehl würde zwei Zyklen dauern ( sowohl in Verzweigungs- als auch in Durchfallfällen). Der Code hatte zwei Vergleiche - einen, der normalerweise gleich war; der andere, ungleich. In beiden Fällen war der Code für seltene Fälle mehr als 128 Byte entfernt. Also ...
Supercat
cjne r0,expected_value,first_comp_springboard/.../ cjne r1,unexpected_value,second_comp_fallthrough// `ajmp second_comp_target` // first_comp_springboard: ajmp first_comp_target// second_comp_fallthrough: .... Kein sehr schönes Codierungsmuster, aber wenn einzelne Zyklen zählen, macht man solche Dinge. In den 1960er Jahren waren solche Optimierungsstufen natürlich wichtiger als heute, zumal moderne CPUs häufig seltsame Optimierungen erfordern und Just-in-Time-Kompilierungssysteme möglicherweise Optimierungscode für CPUs anwenden können, die zu diesem Zeitpunkt nicht vorhanden waren Der betreffende Code wurde geschrieben.
Supercat
34

Go To kann in bestimmten Fällen eine Art Ersatz für die "echte" Ausnahmebehandlung bieten. Erwägen:

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. Lassen Sie sich also nicht zu sehr auf die Details ein. Aber denken Sie an eine Alternative, die ich allzu oft im Produktionscode von Codierern gesehen habe, die absurde Längen gegangen sind, 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);

Funktionell macht dieser Code genau das Gleiche. Tatsächlich ist der vom Compiler generierte Code nahezu identisch. In dem Eifer des Programmierers, Nogoto (den gefürchteten Gott der akademischen Zurechtweisung) zu beschwichtigen , hat dieser Programmierer jedoch die zugrunde liegende Redewendung, die die whileSchleife darstellt, vollständig gebrochen und eine echte Zahl für die Lesbarkeit des Codes angegeben. Das ist nicht besser.

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

tylerl
quelle
1
Obwohl ich dem, was Sie hier sagen, eher zustimme, macht die Tatsache, dass sich die breakAnweisungen innerhalb einer Kontrollstruktur befinden, genau klar, was sie tun. In diesem gotoBeispiel muss die Person, die den Code liest, den Code durchsehen, um die Bezeichnung zu finden, die theoretisch tatsächlich vor der Kontrollstruktur liegen könnte. Ich habe nicht genug Erfahrung mit altmodischem C, um zu beurteilen, dass eines definitiv besser ist als das andere, aber es gibt Kompromisse in beiden Richtungen.
Vivian River
5
@ DanielAllenLangdon: Die Tatsache, dass sich die breaks in einer Schleife befinden, macht genau klar, dass sie die Schleife verlassen . Das ist nicht "genau das, was sie tun", da es in Wirklichkeit überhaupt keine Schleife gibt! Nichts darin hat jemals die Chance, sich zu wiederholen, aber das ist bis zum Ende nicht klar. Die Tatsache, dass Sie eine "Schleife" haben, die nie mehr als einmal ausgeführt wird, bedeutet, dass Kontrollstrukturen missbraucht werden. Mit dem gotoBeispiel kann der Programmierer sagen goto error_handler;. Es ist expliziter und noch weniger schwer zu befolgen. (Strg + F, "error_handler:", um das Ziel zu finden. Versuchen Sie dies mit "}".)
cHao
1
Ich habe einmal einen Code gesehen, der Ihrem zweiten Beispiel in einem Flugsicherungssystem ähnelt - weil "goto ist nicht in unserem Lexikon".
QED
30

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 den Vorschlag von Dijkstra und beschreibt die Umstände, unter denen dies gültig ist. Er gibt aber auch eine Reihe von Gegenbeispielen (Probleme und Algorithmen) an, die nicht einfach nur mit strukturierten Schleifen reproduziert werden können.

Der Artikel enthält eine vollständige Beschreibung des Problems, den Verlauf, Beispiele und Gegenbeispiele.

Bruno Ranschaert
quelle
25

Goto als hilfreich angesehen.

Ich habe 1975 mit dem Programmieren begonnen. Für Programmierer aus den 1970er Jahren sagten die Worte "gehe als schädlich" mehr oder weniger, dass neue Programmiersprachen mit modernen Kontrollstrukturen einen Versuch wert waren. Wir haben die neuen Sprachen ausprobiert. Wir haben schnell konvertiert. Wir sind nie zurückgegangen.

Wir sind nie zurückgegangen, aber wenn Sie jünger sind, waren Sie noch nie dort.

Ein Hintergrund in alten Programmiersprachen ist möglicherweise nur als Indikator für das Alter des Programmierers sehr nützlich. Ungeachtet dessen fehlt jüngeren Programmierern dieser Hintergrund, so dass sie die Botschaft, die der Slogan "Goto als schädlich" an das beabsichtigte Publikum zum Zeitpunkt seiner Einführung übermittelte , nicht mehr verstehen .

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

Dieser spezielle Slogan "Goto gilt als schädlich" hat jedoch ein Eigenleben für Untote aufgenommen.

Kann man nicht missbraucht werden? Antwort: sicher, aber was nun? Praktisch jedes Programmierelement kann missbraucht werden. Die Demütigen boolzum Beispiel werden häufiger missbraucht, als einige von uns glauben möchten.

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

Das größte Problem mit goto ist wahrscheinlich nicht technisch, sondern sozial. Programmierer, die nicht viel wissen, scheinen manchmal das Gefühl zu haben, dass sie durch die Ablehnung von goto klug klingen. Möglicherweise müssen Sie solche Programmierer von Zeit zu Zeit zufriedenstellen. So ist das Leben.

Das Schlimmste an goto heute ist, dass es nicht genug verwendet wird.

thb
quelle
24

Von Jay Ballou angezogen, 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 nicht besprochen habe, ist die Art von Code, der, obwohl nicht gerade üblich, in Fortran-Lehrbüchern gelehrt wurde. Dinge wie der erweiterte Bereich einer DO-Schleife und offen codierter 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 in einer einzigen Funktion.

Beachten Sie, dass das ausgezeichnete, aber veraltete (und vergriffene) Buch ' The Elements of Programming Style, 2. Aufl. ' Von Kernighan & Plauger einige Beispiele aus der Praxis des Missbrauchs von GOTO aus Programmierlehrbüchern seiner Zeit (Ende der 70er Jahre) enthält. 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 diesen Unsinn war die gute altmodische Lochkarte. Möglicherweise stellen Sie fest, dass sich die Beschriftungen (nicht in der richtigen Reihenfolge, da dies ein kanonischer Stil war!) In Spalte 1 befinden (tatsächlich mussten sie sich in den Spalten 1 bis 5 befinden) und der Code in den Spalten 7 bis 72 (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 hatten und ein paar Karten (Zeilen) in die Mitte einer Schleife einfügen müssten, müssten Sie nach diesen zusätzlichen Zeilen alles neu starten. Wenn Sie jedoch eine Karte durch das GOTO-Material ersetzt haben, können Sie vermeiden, dass alle Karten neu geordnet werden. Sie haben die neuen Karten am Ende der Routine nur mit neuen Sequenznummern versehen. Betrachten Sie es als den ersten Versuch von "Green Computing"

Oh, Sie könnten auch bemerken, dass ich betrüge und nicht schreie - Fortran IV wurde normalerweise in Großbuchstaben geschrieben.

Open-Coded-Subroutine

       ...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 Subroutine. Es war ein Code, der wie eine Unterroutine ausgeführt, aber im Hauptteil einer Funktion geschrieben wurde. (Fortran hatte auch Anweisungsfunktionen - eingebettete Funktionen, die in eine einzelne Zeile passten.)

Es gab schlechtere Konstrukte als das berechnete goto - Sie konnten Variablen Beschriftungen zuweisen und dann ein zugewiesenes goto verwenden. Googeln zugewiesen goto sagt mir, dass es aus Fortran 95 gelöscht wurde. Kreide eins für die strukturierte Programmierrevolution, die mit Dijkstras "GOTO Considered Harmful" Brief oder Artikel öffentlich begonnen haben könnte.

Ohne ein gewisses Wissen über die Art der Dinge, die in Fortran (und in anderen Sprachen, von denen die meisten zu Recht auf der Strecke geblieben sind) getan wurden, ist es für uns Neulinge schwierig, den Umfang des Problems zu verstehen, mit dem sich Dijkstra befasste. Heck, ich habe erst zehn Jahre nach Veröffentlichung dieses Briefes mit dem Programmieren begonnen (aber ich hatte das Unglück, eine Weile in Fortran IV zu programmieren).

Jonathan Leffler
quelle
2
Wenn Sie ein Beispiel für einen Code sehen möchten, der goto"in the wild" verwendet, zeigt die Frage " Gesucht: Funktionierender Bose-Hibbard-Sortieralgorithmus" einen 1963 veröffentlichten (Algol 60) Code. Das ursprüngliche Layout ist nicht mit modernen Codierungsstandards vergleichbar. Der geklärte (eingerückte) Code ist immer noch ziemlich unergründlich. Die gotoAussagen dort tun es (sehr) schwer zu verstehen , was der Algorithmus ist oben.
Jonathan Leffler
1
Da es zu jung war, um etwas in der Nähe von Lochkarten erlebt zu haben, war es aufschlussreich, über das Problem des erneuten Lochens zu lesen. +1
Qix - MONICA wurde am
20

Es gibt keine Dinge, die GOTO als schädlich erachtet .

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

Es gibt jedoch viele Tools in der Programmierwelt, die dazu neigen, mehr missbraucht als verwendet zu werden , und GOTO ist eines davon. Die WITH- Anweisung von Delphi ist eine andere.

Persönlich verwende ich auch keinen typischen Code , aber ich hatte die seltsame Verwendung von GOTO und WITH , die gerechtfertigt war, und eine alternative Lösung hätte mehr Code enthalten.

Die beste Lösung wäre, wenn der Compiler Sie nur warnt, dass das Schlüsselwort fehlerhaft ist , und Sie müssten ein paar Pragma-Anweisungen um die Anweisung setzen, um die Warnungen zu entfernen.

Es ist, als würde man seinen Kindern sagen, sie sollen nicht mit einer Schere rennen . Scheren sind nicht schlecht, aber ihre Verwendung ist möglicherweise nicht der beste Weg, um Ihre Gesundheit zu erhalten.

Lasse V. Karlsen
quelle
Das Problem mit GOTO ist, dass es wichtige Invarianten bricht, die wir mit modernen Programmiersprachen für selbstverständlich halten. Wenn ich zum Beispiel eine Funktion aufrufe, gehen wir davon aus, dass die Funktion nach Abschluss der Funktion entweder normal oder über ein außergewöhnliches Abwickeln des Stapels die Kontrolle an den Aufrufer zurückgibt. Wenn diese Funktion GOTO falsch verwendet, ist dies natürlich nicht mehr der Fall. Dies macht es sehr schwierig, über unseren Code nachzudenken. Es reicht nicht aus, den Missbrauch von GOTO sorgfältig zu vermeiden. Das Problem tritt immer noch auf, wenn GOTO durch unsere Abhängigkeiten missbraucht wird ...
Jonathan Hartley
... Um zu wissen, ob wir über unsere Funktionsaufrufe nachdenken können, müssen wir jede Zeile des Quellcodes jeder unserer transitiven Abhängigkeiten untersuchen und sicherstellen, dass sie GOTO nicht missbrauchen. Allein die Existenz von GOTO in der Sprache hat unsere Fähigkeit, sicher über unseren eigenen Code nachzudenken, gebrochen, selbst wenn wir ihn selbst perfekt (oder nie) verwenden. Aus diesem Grund ist GOTO nicht nur ein Werkzeug, das sorgfältig verwendet werden muss. Es ist systematisch gebrochen und seine Existenz in einer Sprache wird einseitig als schädlich angesehen.
Jonathan Hartley
Selbst wenn das gotoSchlüsselwort aus C # entfernt wurde, existiert das Konzept "hier springen" in IL immer noch. Eine Endlosschleife kann leicht ohne das Schlüsselwort goto erstellt werden. Wenn dieser Mangel an Garantie, dass der aufgerufene Code zurückgegeben wird, Ihrer Meinung nach bedeutet, dass "nicht in der Lage ist, über den Code nachzudenken", dann würde ich sagen, dass wir diese Fähigkeit nie hatten.
Lasse V. Karlsen
Ah, Sie haben aufschlussreich die Quelle unserer Fehlkommunikation gefunden. Das gotoin C # ist kein echtes "goto" im ursprünglichen Sinne des Wortes. Es ist eine viel schwächere Version, die nur das Springen innerhalb einer Funktion erlaubt. Der Sinn, in dem ich "goto" verwende, ermöglicht es, überall im Prozess zu springen. Obwohl C # ein "goto" -Schlüsselwort hat, hatte es wohl noch nie ein echtes goto. Ja, auf IL-Ebene ist ein echtes Goto verfügbar, genauso wie wenn eine Sprache bis zur Assemblierung kompiliert wird. Der Programmierer ist jedoch davor geschützt und kann ihn unter normalen Umständen nicht verwenden. Daher zählt er nicht.
Jonathan Hartley
Übrigens ist die Meinung, die ich hier beschreibe, nicht ursprünglich meine, sondern der Kern von Dijkstras Originalarbeit aus dem Jahr 1967, die meiner Meinung nach von seinem Herausgeber "Goto als schädlich" umbenannt wurde und seit 50 Jahren zu einem so oft zitierten Mem geworden ist Jahre, gerade weil es so revolutionär, aufschlussreich und allgemein anerkannt war.
Jonathan Hartley
17

Es war nie so lange, wie Sie selbst denken konnten.

stesch
quelle
17

Seit ich ein paar Dinge im Linux-Kernel gemacht habe, stören mich gotos nicht mehr so ​​sehr wie früher. Zuerst war ich irgendwie entsetzt zu sehen, dass sie (Kernel-Leute) meinem Code Gotos hinzugefügt haben. Seitdem habe ich mich in einigen begrenzten Zusammenhängen an die Verwendung von gotos gewöhnt und werde sie jetzt gelegentlich selbst verwenden. In der Regel ist es ein Goto, der zum Ende einer Funktion springt, um eine Art Bereinigung und Rettung durchzuführen, anstatt dieselbe Bereinigung und Rettung an mehreren Stellen in der Funktion zu duplizieren. Und normalerweise ist es nicht groß genug, um an eine andere Funktion übergeben zu werden - z. B. ist das Freigeben einiger lokal (k) mallocierter Variablen ein typischer Fall.

Ich habe Code geschrieben, der setjmp / longjmp nur einmal verwendet hat. Es war in einem MIDI-Drum-Sequenzer-Programm. Die Wiedergabe erfolgte in einem von allen Benutzerinteraktionen getrennten Prozess, und der Wiedergabevorgang verwendete 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 nur einen Longjmp "Zurück zum Anfang" durch, um von vorne zu beginnen, anstatt ein kompliziertes Abwickeln dessen, wo es gerade ausgeführt wurde, als der Benutzer wollte, dass es gestoppt wurde. Es hat großartig funktioniert, war einfach und ich hatte in diesem Fall nie Probleme oder Fehler damit.

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

Edit: 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 ist, aber ich hatte vergessen, dass siglongjmp überhaupt existiert.)

smcameron
quelle
15

Wenn Sie eine VM in C schreiben, stellt sich heraus, dass Sie (gccs) berechnete gotos wie folgt verwenden:

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 Schalter innerhalb einer Schleife.

vt.
quelle
Einziges Problem, dass es nicht Standard ist, oder?
Calmarius
&&op_incsicherlich nicht kompiliert, weil (links) &einen l-Wert erwartet, aber (rechts) &einen r-Wert ergibt.
Fredoverflow
7
@FredO: Es ist ein spezieller GCC-Operator. Ich würde diesen Code jedoch unter allen Umständen ablehnen, außer unter den schlimmsten Umständen, da ich mit Sicherheit nicht verstehen kann, wie wtf vor sich geht.
Welpe
Dies ist zwar ein Beispiel (maximale Geschwindigkeitsoptimierung), das sowohl die Verwendung von gotos als auch des kryptischen Codes rechtfertigt, es sollte jedoch dennoch stark kommentiert werden, um die Notwendigkeit der Optimierung, die grobe Funktionsweise und die beste zeilenweise Erklärung zu erläutern Kommentare, die gemacht werden können. Es schlägt also immer noch fehl, aber weil Wartungsprogrammierer im Dunkeln bleiben. Aber ich mag das VM-Beispiel. Vielen Dank.
MicroservicesOnDDD
14

Weil gotokann für verwirrende Metaprogrammierung verwendet werden

Gotoist sowohl ein Steuerausdruck auf hoher als auch auf niedriger Ebene und hat daher einfach kein geeignetes Entwurfsmuster, das für die meisten Probleme geeignet ist.

Es ist niedrig in dem Sinne, dass ein goto eine primitive Operation ist, die etwas Höheres wie whileoder foreachoder etwas implementiert .

Es ist auf hoher Ebene in dem Sinne, dass es, wenn es auf bestimmte Weise verwendet wird, Code benötigt, der in einer klaren Reihenfolge und ohne Unterbrechung ausgeführt wird, mit Ausnahme von strukturierten Schleifen, und es in logische Teile umwandelt, die mit genügend gotos ein Greifer sind - Beutel mit Logik, die dynamisch wieder zusammengesetzt wird.

Es gibt also eine prosaische und eine böse Seite goto.

Die prosaische Seite ist, dass ein nach oben zeigender Goto eine vollkommen vernünftige Schleife implementieren kann und ein nach unten gerichteter Goto eine vollkommen vernünftige Schleife ausführen kann breakoder return. Natürlich ein tatsächlichen while, breakoder returnwäre viel besser lesbar sein, da der arme Mensch würde nicht die Wirkung, das hat zu simulieren , gotoum das große Bild zu bekommen. Also eine schlechte Idee im Allgemeinen.

Die böse Seite beinhaltet eine Routine, die goto nicht für eine Weile, Pause oder Rückkehr verwendet, sondern für die sogenannte Spaghetti-Logik . In diesem Fall konstruiert der goto-happy-Entwickler Codeteile aus einem Labyrinth von goto's, und der einzige Weg, dies zu verstehen, besteht darin, es mental als Ganzes zu simulieren, eine schrecklich anstrengende Aufgabe, wenn es viele goto's gibt. Ich meine, stellen Sie sich die Mühe vor, Code zu bewerten, bei dem elsedas nicht genau umgekehrt ist if, wo verschachtelte ifs in einigen Dingen, die vom Äußeren abgelehnt wurden if, usw. usw. zulassen könnten .

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 gemacht haben, die ihren Versionen von unterliegen if-then-else. Die einzige Möglichkeit, einen bedingten Block auszuführen, bestand darin, gotoihn mit einer inversen Bedingung zu umgehen. Wahnsinnig, ich weiß, aber ich habe einige alte Spezifikationen gelesen. Denken Sie daran, dass die ersten Computer in binärem Maschinencode programmiert wurden. Ich nehme an, dass jede Art von HLL ein Lebensretter war. Ich denke, sie waren nicht zu wählerisch in Bezug auf die HLL-Funktionen, die sie haben.

Nachdem ich alles gesagt hatte, was ich gotoin jedes Programm gesteckt hatte, schrieb ich "nur um die Puristen zu ärgern" .

DigitalRoss
quelle
4
+1 für nervige Puristen! :-)
Lumi
10

Den Programmierern die Verwendung der GOTO-Anweisung zu verweigern, ist wie einem Schreiner zu sagen, er solle keinen Hammer verwenden, da dies die Wand beschädigen könnte, während er in einen Nagel hämmert. Ein echter Programmierer weiß, wie und wann ein GOTO zu verwenden ist. Ich bin einigen dieser sogenannten "strukturierten Programme" gefolgt. Ich sehe solch schrecklichen Code, nur um die Verwendung eines GOTO zu vermeiden, dass ich den Programmierer erschießen könnte. Ok, zur Verteidigung der anderen Seite habe ich auch echten Spaghetti-Code gesehen und wieder sollten diese Programmierer auch erschossen werden.

Hier ist nur ein kleines Beispiel für 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
CurtTampa
quelle
3
DO CRT 'Ist das richtig? (J / N): ': EINGABE YORN BIS YORN =' Y 'ODER YORN =' N '; usw.
joel.neely
5
In der Tat, aber was noch wichtiger ist, weiß ein echter Programmierer, wann er a nicht verwenden soll goto- und weiß warum . Das Vermeiden eines Tabu-Sprachkonstrukts, weil $ programming_guru dies sagte, ist genau die Definition der Frachtkult-Programmierung.
Piskvor verließ das Gebäude am
1
Das ist eine gute Analogie. Um einen Nagel zu schlagen, ohne den Untergrund zu beschädigen, entfernen Sie den Hammer nicht. Sie verwenden vielmehr ein einfaches Werkzeug, das als Nagelschlag bezeichnet wird. Dies ist ein Metallstift mit einem sich verjüngenden Ende, das an seiner Spitze eine hohle Vertiefung aufweist, um sicher mit dem Nagelkopf verbunden zu werden (diese Werkzeuge sind in verschiedenen Größen für verschiedene Nägel erhältlich). Das andere, stumpfe Ende des Nagelstempels wird mit einem Hammer geschlagen.
Kaz
9

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

Ironischerweise führte das Eliminieren des Goto zu einem Fehler: Der Spinlock-Aufruf wurde weggelassen.

Jay Ballou
quelle
+1 für 'Eliminierung des Goto führte einen Fehler ein'
QED
7

Das Originalpapier sollte als "Unbedingte GOTO als schädlich angesehen" betrachtet werden. Es befürwortete insbesondere eine Form der Programmierung, die auf bedingten ( if) und iterativen ( while) Konstrukten basiert , und nicht auf dem für frühen Code üblichen Test-and-Jump. gotoist in einigen Sprachen oder Umständen immer noch nützlich, in denen keine geeignete Kontrollstruktur vorhanden ist.

John Millikin
quelle
6

Ich bin damit einverstanden, dass Goto nur verwendet werden kann, wenn Sie sich mit Fehlern befassen müssen und jeder einzelne Punkt, an dem ein Fehler auftritt, 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 auf die entgegengesetzte Weise freigeben.

Einige Codes erfordern ein sehr seltsames Muster beim Abrufen dieser Ressourcen, und Sie können nicht einfach eine leicht zu wartende und verständliche Kontrollstruktur schreiben, um sowohl das Abrufen als auch das Freigeben dieser Ressourcen korrekt zu handhaben und einen Deadlock zu vermeiden.

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

-Adam

Adam Davis
quelle
5

Eine moderne GOTO-Verwendung besteht darin, dass der C # -Compiler Zustandsmaschinen für Enumeratoren erstellt, die durch Yield Return definiert sind.

GOTO sollte von Compilern und nicht von Programmierern verwendet werden.

Brian Leahy
quelle
5
Wer genau erstellt Ihrer Meinung nach die Compiler?
Tloach
10
Compiler natürlich!
Seiti
3
Ich denke, er meint "GOTO ist etwas, das nur von Code verwendet werden sollte, der von einem Compiler ausgegeben wird".
Simon Buchan
Dies ist ein Fall gegen goto. Wo wir gotoin einer von Hand codierten Zustandsmaschine einen Enumerator implementieren könnten , können wir yieldstattdessen einfach verwenden .
Jon Hanna
Aktivieren Sie viele Zeichenfolgen (um das Kompilieren auf if-else zu verhindern). Standardmäßig wird case case kompiliert, um mit der goto-Anweisung zu wechseln.
M. Kazem Akhgary 24.
5

Bis C und C ++ (unter anderem Schuldige) Pausen markiert haben und fortfahren, wird goto weiterhin eine Rolle spielen.

DrPizza
quelle
So beschriftet break or continue wäre anders als goto wie?
Matthew Whited
3
Sie erlauben keine völlig willkürlichen Sprünge im Kontrollfluss.
DrPizza
5

Wenn GOTO selbst böse wäre, wären Compiler böse, weil sie JMPs generieren. Wenn das Springen in einen Codeblock, insbesondere nach einem Zeiger, von Natur aus böse wäre, wäre die RETurn-Anweisung böse. Vielmehr besteht das Böse im Missbrauchspotential.

Manchmal musste ich Apps schreiben, die eine Reihe von Objekten verfolgen mussten, bei denen jedes Objekt als Reaktion auf Ereignisse einer komplizierten Folge von Zuständen folgen musste, aber das Ganze war definitiv ein einzelner Thread. Eine typische Folge von Zuständen, wenn sie im Pseudocode dargestellt werden, wäre:

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 sicher, dass dies nicht neu ist, aber ich habe es in C (++) so gehandhabt, dass ich einige Makros definiert habe:

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

Mit einer Variation davon kann es CALL und RETURN geben, so dass einige Zustandsautomaten sich wie Unterprogramme anderer Zustandsautomaten verhalten können.

Ist es ungewöhnlich Ja. Benötigt der Betreuer etwas Lernen? Ja. Lohnt sich dieses Lernen? Ich glaube schon. Könnte es ohne GOTOs gemacht werden, die in Blöcke springen? Nee.

Mike Dunlavey
quelle
1
Das Vermeiden eines Sprachmerkmals aus Angst ist ein Zeichen für eine Schädigung des Gehirns. Das ist auch geschickter als die Hölle.
Vektor Gorgoth
4

Ich vermeide es, da ein Mitarbeiter / Manager seine Verwendung zweifellos entweder in einer Codeüberprüfung oder wenn er darüber stolpert, in Frage stellen wird. Obwohl ich denke, dass es Verwendungszwecke hat (zum Beispiel den Fall der Fehlerbehandlung), werden Sie einem anderen Entwickler zuwiderlaufen, der ein Problem damit hat.

Es lohnt sich nicht.

Aardvark
quelle
Das Schöne an Try ... Catch-Blöcken in C # ist, dass sie sich darum kümmern, den Stapel und andere zugewiesene Ressourcen (als Abwickeln des Stapels bezeichnet) zu bereinigen, wenn die Ausnahme zu einem Ausnahmebehandler sprudelt. Dies macht einen Try ... Catch viel besser als Goto. Wenn Sie also Try ... Catch haben, verwenden Sie ihn.
Scott Whitlock
Ich habe eine bessere Lösung: Wickeln Sie den Codeabschnitt, aus dem Sie herausspringen möchten, in ein 'do {...} while (0);' Schleife. Auf diese Weise können Sie auf die gleiche Weise wie ein goto ohne den Aufwand der try / catch-Schleife herausspringen (ich weiß nichts über C #, aber in C ++ ist try kostengünstig und catch ist teuer, so scheint es übermäßig, um eine Ausnahme auszulösen, bei der ein einfacher Sprung ausreichen würde).
Jim Dovey
10
Jim, das Problem dabei ist, dass es nichts weiter als ein dummer Umweg ist, um ein Goto zu bekommen.
Codierung mit Stil
4

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 es operieren. Wenn sich das Objekt in einem Zustand befand, konnte ich eine schnelle Version der Operation ausführen, andernfalls musste ich eine langsame Version der Operation ausführen. Die Sache war, dass es in einigen Fällen mitten im langsamen Betrieb möglich war zu erkennen, dass dies mit dem schnellen Betrieb hätte geschehen können.

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 war ein geschwindigkeitskritischer Teil des Echtzeit-UI-Codes, daher denke ich ehrlich, dass ein GOTO hier gerechtfertigt war.

Hugo

Raketenmagnet
quelle
Die Nicht-GOTO-Methode wäre die Verwendung einer Funktion fast_calculations, die einen gewissen Overhead verursacht. Wahrscheinlich in den meisten Fällen nicht bemerkbar, aber wie Sie sagten, war dies geschwindigkeitskritisch.
Kyle Cronin
1
Nun, das ist kaum überraschend. Jeder Code, der absolut verrückte Leistungsrichtlinien enthält, verstößt immer gegen so ziemlich alle Best Practices. Best Practices sind Erreichbarkeit und Wartbarkeit, nicht 10 weitere Millisekunden oder 5 Byte mehr RAM.
Jonathon
1
@ JonathonWisnoski, legitime Verwendungen von goto eliminieren auch wahnsinnige Mengen an Spagetti-Code, die sich in die Variablennester einer Ratte einmischen, um zu verfolgen, wohin wir gehen.
vonbrand
4

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

Ich persönlich benutze es nie explizit, brauche es nie.

Adam Houldsworth
quelle
4

Eine Sache, die ich aus keiner der Antworten hier gesehen habe, ist, dass eine "goto" -Lösung oft effizienter ist als eine der oft erwähnten strukturierten Programmierlösungen.

Betrachten Sie den Fall mit vielen verschachtelten Schleifen, in dem die Verwendung von 'goto' anstelle einer Reihe von if(breakVariable)Abschnitten offensichtlich effizienter ist. Die Lösung "Setzen Sie Ihre Schleifen in eine Funktion und verwenden Sie return" ist oft völlig unvernünftig. In dem wahrscheinlichen Fall, dass die Schleifen lokale Variablen verwenden, müssen Sie sie jetzt alle über Funktionsparameter übergeben, um möglicherweise eine Menge zusätzlicher Kopfschmerzen zu bewältigen, die sich daraus ergeben.

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 war, die in vielen Sprachen nicht verfügbar ist. Die Anzahl der Überprüfungen und zusätzlichen Variablen, die erforderlich sind, um dasselbe zu erreichen, ist weitaus schlechter als die ein oder zwei Anweisungen, um den Sprung auszuführen, und auch hier ist die Lösung für zusätzliche Funktionen überhaupt keine Lösung. Sie können mir nicht sagen, dass dies überschaubarer oder lesbarer ist.

Jetzt sind Code-Speicherplatz, Stack-Nutzung und Ausführungszeit für viele Programmierer in vielen Situationen möglicherweise nicht wichtig genug. Wenn Sie sich jedoch in einer eingebetteten Umgebung mit nur 2 KB Code-Speicherplatz befinden, müssen Sie 50 Byte zusätzliche Anweisungen verwenden, um eine klar definierte zu vermeiden 'goto' ist einfach lächerlich, und dies ist keine so seltene Situation, wie viele hochrangige Programmierer glauben.

Die Aussage, dass "goto is schädlich" ist, war sehr hilfreich bei der Umstellung auf strukturierte Programmierung, auch wenn es sich immer um eine Überverallgemeinerung handelte. Zu diesem Zeitpunkt haben wir alle genug davon gehört, um vorsichtig zu sein (wie wir sollten). Wenn es offensichtlich das richtige Werkzeug für den Job ist, müssen wir uns nicht davor fürchten.

gkimsey
quelle
3

Sie können es verwenden, um aus einer tief verschachtelten Schleife auszubrechen, aber meistens kann Ihr Code ohne tief verschachtelte Schleifen sauberer gestaltet werden.

Brian R. Bondy
quelle