Zu welchen Fehlern führen "goto" -Anweisungen? Gibt es historisch bedeutsame Beispiele?

103

Ich verstehe, dass außer zum Ausbrechen von in Schleifen verschachtelten Schleifen; Die gotoAussage wird als fehleranfälliger Programmierstil umgangen und verleumdet, um niemals verwendet zu werden.

XKCD Alt Text: "Neal Stephenson findet es niedlich, seine Labels 'dengo' zu nennen"
Siehe den Original-Comic unter: http://xkcd.com/292/

Weil ich das früh gelernt habe; Ich habe keine wirklichen Einsichten oder Erfahrungen darüber, zu welchen Arten von Fehlern sie gototatsächlich führen. Also, worüber reden wir hier:

  • Instabilität?
  • Nicht wartbarer oder nicht lesbarer Code?
  • Sicherheitslücken?
  • Noch etwas ganz anderes?

Zu welchen Fehlern führen "goto" -Anweisungen tatsächlich? Gibt es historisch bedeutsame Beispiele?

Akiva
quelle
33
Haben Sie Dijkstras Originalartikel zu diesem Thema gelesen? (Das ist eine Ja / Nein-Frage und jede andere Antwort als ein eindeutiger Moment. "Ja" ist ein "Nein".)
John R. Strohm
2
Ich nehme an, ein Alarmausgang ist, wenn etwas Katastrophales passiert. von wo du niemals zurückkehren wirst. Wie Stromausfall, verschwinden Geräte oder Dateien. Einige Arten von "on error goto" ... Aber ich denke, die meisten "fast katastrophalen" Fehler können heutzutage durch "try - catch" - Konstrukte behoben werden.
Lenne
13
Das berechnete GOTO hat noch niemand erwähnt. Als die Erde noch jung war, hatte BASIC Zeilennummern. Sie könnten Folgendes schreiben: 100 N = A * 100; GOTO N. Versuchen Sie, das zu debuggen :)
Mcottle
7
@ JohnR.Strohm Ich habe die Zeitung von Dijkstra gelesen. Es wurde 1968 geschrieben und deutet darauf hin, dass der Bedarf an gotos in Sprachen reduziert werden könnte, die erweiterte Funktionen wie switch-Anweisungen und -Funktionen enthalten. Es wurde als Antwort auf Sprachen geschrieben, in denen goto die primäre Methode zur Flusskontrolle war, und konnte verwendet werden, um zu jedem Punkt im Programm zu springen. Während in Sprachen, die entwickelt wurden, nachdem dieses Papier geschrieben wurde, z. B. C, goto nur zu Stellen im selben Stapelrahmen springen kann und im Allgemeinen nur verwendet wird, wenn andere Optionen nicht gut funktionieren. (Fortsetzung ...)
Ray
10
(... Fortsetzung) Seine Punkte waren zu der Zeit gültig, sind aber nicht mehr relevant, unabhängig davon, ob goto eine gute Idee ist oder nicht. Wenn wir so oder so argumentieren wollen, müssen Argumente vorgebracht werden, die sich auf moderne Sprachen beziehen. Haben Sie übrigens Knuths "Strukturierte Programmierung mit goto-Anweisungen" gelesen?
Ray

Antworten:

2

Hier ist eine Möglichkeit, es zu betrachten, die ich in den anderen Antworten noch nicht gesehen habe.

Es geht um Umfang. Eine der wichtigsten Säulen einer guten Programmierpraxis ist es, den Rahmen eng zu halten. Sie brauchen engen Spielraum, weil Sie nicht die geistige Fähigkeit haben, mehr als nur ein paar logische Schritte zu überwachen und zu verstehen. Sie erstellen also kleine Blöcke (von denen jeder zu "einer Sache" wird) und bauen dann mit diesen größere Blöcke, die zu einer Sache werden, und so weiter. So bleiben die Dinge überschaubar und nachvollziehbar.

Ein goto erweitert effektiv den Umfang Ihrer Logik auf das gesamte Programm. Dies wird Ihr Gehirn mit Sicherheit für alle Programme außer den kleinsten, die nur wenige Zeilen umfassen, besiegen.

Sie können also nicht mehr erkennen, ob Sie einen Fehler begangen haben, da es einfach zu viel gibt, um nach Ihrem armen kleinen Kopf zu suchen. Dies ist das eigentliche Problem, Fehler sind nur eine wahrscheinliche Folge.

Martin Maat
quelle
Nicht lokal gehe zu?
Deduplizierer
thebrain.mcgill.ca/flash/capsules/experience_jaune03.html << Der menschliche Verstand kann durchschnittlich 7 Dinge auf einmal verfolgen. Ihre Überlegungen spielen bei dieser Einschränkung eine Rolle, da durch die Erweiterung des Anwendungsbereichs viel mehr Dinge hinzugefügt werden müssen, die im Auge behalten werden müssen. Daher sind die Arten von Fehlern, die eingeführt werden, wahrscheinlich Auslassungen und Vergesslichkeit. Sie bieten auch eine mildernde Strategie und bewährte Verfahren im Allgemeinen an, um "Ihren Anwendungsbereich eng zu halten". Als solches akzeptiere ich diese Antwort. Gute Arbeit Martin.
Akiva
1
Wie cool ist das?! Unter über 200 abgegebenen Stimmen, die mit nur 1 gewonnen haben!
Martin Maat
68

Es ist nicht so, dass das an gotosich schlecht ist. (Immerhin ist jeder Sprungbefehl in einem Computer ein Goto.) Das Problem ist, dass es einen menschlichen Programmierstil gibt, der eine strukturierte Programmierung vorwegnimmt, was als "Flussdiagramm" -Programmierung bezeichnet werden könnte.

In der Ablaufdiagrammprogrammierung (die Menschen meiner Generation gelernt haben und für das Apollo Moon-Programm verwendet wurden) erstellen Sie ein Diagramm mit Blöcken für Anweisungsausführungen und Rauten für Entscheidungen, und Sie können sie mit überall verlaufenden Linien verbinden. (Sogenannter "Spaghetti-Code".)

Das Problem mit dem Spaghetti-Code ist, dass Sie als Programmierer "wissen" können, dass es richtig ist, aber wie können Sie es sich selbst oder anderen beweisen ? Tatsächlich kann es tatsächlich zu einem möglichen Fehlverhalten kommen, und Ihr Wissen, dass es immer richtig ist, kann falsch sein.

Es folgte eine strukturierte Programmierung mit Anfangs- und Endblöcken, z. B. if-else und so weiter. Diese hatten den Vorteil, dass Sie immer noch alles in ihnen tun können, aber wenn Sie überhaupt vorsichtig waren, können Sie sicher sein, dass Ihr Code korrekt war.

Natürlich können die Leute auch ohne Spaghetti-Code schreiben goto. Die übliche Methode besteht darin, ein zu schreiben while(...) switch( iState ){..., wobei unterschiedliche Fälle die iStateWerte unterschiedlich festlegen. Tatsächlich könnte man in C oder C ++ ein Makro schreiben, um das zu tun, und es benennen GOTO, also gotoist es eine Unterscheidung ohne Unterschied , zu sagen, dass Sie nicht verwenden .

Als Beispiel dafür, wie Code-Proving uneingeschränkt ausschließen kann goto, bin ich vor langer Zeit auf eine Kontrollstruktur gestoßen, die für sich dynamisch ändernde Benutzeroberflächen nützlich ist. Ich nannte es differenzielle Ausführung . Es ist voll Turing-universal, aber seine Richtigkeit Beweis hängt von reiner strukturierter Programmierung - nein goto, return, continue, break, oder Ausnahmen.

Bildbeschreibung hier eingeben

Mike Dunlavey
quelle
49
Correctness lässt sich nicht bewährt , außer in den banalsten von Programmen, auch wenn die strukturierte Programmierung verwendet wird , sie zu schreiben. Sie können nur Ihr Selbstvertrauen steigern. Die strukturierte Programmierung leistet dies.
Robert Harvey
23
@MikeDunlavey: Verwandte: "Vorsicht vor Fehlern im obigen Code; ich habe es nur als richtig erwiesen, nicht ausprobiert." staff.fnwi.uva.nl/p.vanemdeboas/knuthnote.pdf
Mooing Duck
26
@RobertHarvey Es ist nicht ganz richtig, dass Korrektheitsnachweise nur für triviale Programme praktisch sind. Es sind jedoch spezialisiertere Werkzeuge als die strukturierte Programmierung erforderlich.
Mike Haskel
18
@MSalters: Es ist ein Forschungsprojekt. Das Ziel eines solchen Forschungsprojekts ist es zu zeigen, dass sie einen verifizierten Compiler schreiben könnten , wenn sie die Ressourcen hätten. Wenn Sie sich ihre aktuelle Arbeit ansehen, werden Sie feststellen, dass sie einfach nicht daran interessiert sind, spätere Versionen von C zu unterstützen, sondern dass sie daran interessiert sind, die Korrektheitseigenschaften zu erweitern, die sie nachweisen können. Dabei spielt es keine Rolle, ob sie C90, C99, C11, Pascal, Fortran, Algol oder Plankalkül unterstützen.
Jörg W Mittag
8
@martineau: Ich bin misstrauisch gegenüber diesen "Boson-Phrasen", bei denen Programmierer sich einig sind, dass es schlecht oder gut ist, weil sie sich darauf setzen, wenn sie es nicht tun. Es muss grundlegendere Gründe für die Dinge geben.
Mike Dunlavey
63

Warum ist es gefährlich?

  • gotoverursacht keine Instabilität von selbst. Trotz etwa 100.000 gotos ist der Linux-Kernel immer noch ein Stabilitätsmodell.
  • gotoan sich sollte keine Sicherheitslücken verursachen. In einigen Sprachen kann das Mischen mit try/ catchexception-Verwaltungsblöcken jedoch zu Schwachstellen führen, wie in dieser CERT-Empfehlung erläutert . Mainstream-C ++ - Compiler kennzeichnen und verhindern solche Fehler, ältere oder exotischere Compiler jedoch nicht.
  • gotoverursacht unlesbaren und nicht wartbaren Code. Dies wird auch als Spaghetti-Code bezeichnet , da es wie bei einer Spaghetti-Platte sehr schwierig ist, den Kontrollfluss zu verfolgen, wenn zu viele GOTOS vorhanden sind.

Selbst wenn Sie es schaffen, Spaghetti-Code zu vermeiden, und wenn Sie nur ein paar GOTOS verwenden, können Fehler wie und Ressourcenlecks auftreten:

  • Code unter Verwendung der Strukturprogrammierung mit klar verschachtelten Blöcken und Schleifen oder Schaltern ist leicht zu befolgen. Der Kontrollfluss ist sehr vorhersehbar. Es ist daher einfacher sicherzustellen, dass Invarianten respektiert werden.
  • Mit einer gotoAussage brechen Sie diesen direkten Fluss und brechen die Erwartungen. Beispielsweise werden Sie möglicherweise nicht bemerken, dass Sie noch Ressourcen freigeben müssen.
  • Viele gotoan verschiedenen Orten können Sie zu einem einzelnen Ziel senden. Daher ist es nicht offensichtlich, dass Sie genau wissen, in welchem ​​Zustand Sie sich befinden, wenn Sie diesen Ort erreichen. Das Risiko, falsche / unbegründete Annahmen zu treffen, ist daher recht groß.

Zusätzliche Informationen und Zitate:

C bietet die unbegrenzt missbräuchliche gotoAnweisung und Beschriftungen, zu denen verzweigt werden kann. Formal gotoist das nie notwendig und in der Praxis ist es fast immer einfach, Code ohne es zu schreiben. (...)
Dennoch werden wir einige Situationen vorschlagen, in denen Goto einen Platz finden kann. Am häufigsten wird die Verarbeitung in einigen tief verschachtelten Strukturen abgebrochen, z. B. wenn zwei Schleifen gleichzeitig abgebrochen werden. (...)
Obwohl wir in dieser Angelegenheit nicht dogmatisch sind, sollten goto-Anweisungen, wenn überhaupt, sparsam verwendet werden .

  • James Gosling & Henry McGilton schrieben 1995 in ihrem Whitepaper zur Java-Umgebung :

    No More Goto-Anweisungen
    Java hat keine goto-Anweisung. Studien haben gezeigt, dass goto nicht nur deshalb (falsch) verwendet wird, weil es da ist. Der Wegfall von goto führte zu einer Vereinfachung der Sprache. (...) Studien an ungefähr 100.000 Zeilen C-Code ergaben, dass ungefähr 90 Prozent der goto-Anweisungen nur verwendet wurden, um den Effekt des Ausbrechens verschachtelter Schleifen zu erzielen. Wie oben erwähnt, unterbrechen Sie mehrstufige Anweisungen und entfernen Sie den größten Teil der Notwendigkeit für goto-Anweisungen.

  • Bjarne Stroustrup definiert goto in seinem Glossar mit diesen einladenden Begriffen:

    goto - der berüchtigte goto. In erster Linie nützlich in maschinengeneriertem C ++ - Code.

Wann kann verwendet werden?

Wie K & R bin ich nicht dogmatisch in Bezug auf Gotos. Ich gebe zu, dass es Situationen gibt, in denen man sich das Leben erleichtern könnte.

In der Regel ermöglicht goto in C das Verlassen einer mehrstufigen Schleife oder die Fehlerbehandlung, wobei ein geeigneter Austrittspunkt erreicht werden muss, der alle bisher zugewiesenen Ressourcen freigibt / entsperrt (eine mehrfache Zuweisung in Folge bedeutet mehrere Bezeichnungen). In diesem Artikel werden die verschiedenen Verwendungen von goto im Linux-Kernel quantifiziert.

Persönlich bevorzuge ich es zu vermeiden und in 10 Jahren von C habe ich maximal 10 gotos verwendet. Ich bevorzuge geschachtelte ifs, die meiner Meinung nach besser lesbar sind. Wenn dies zu einer zu tiefen Verschachtelung führen würde, würde ich entweder meine Funktion in kleinere Teile zerlegen oder einen Booleschen Indikator in einer Kaskade verwenden. Die heutigen Compiler zur Optimierung sind clever genug, um fast den gleichen Code wie mit demselben Code zu generieren goto.

Die Verwendung von goto hängt stark von der Sprache ab:

  • In C ++ führt die ordnungsgemäße Verwendung von RAII dazu, dass der Compiler Objekte, die außerhalb des Gültigkeitsbereichs liegen, automatisch zerstört, sodass die Ressourcen / Sperren ohnehin bereinigt werden und keine Notwendigkeit mehr besteht, weiterzugehen.

  • In Java gibt es keine Notwendigkeit für goto (siehe Java des Autor Zitat oben und diese ausgezeichnete Stack - Überlaufes Antwort ): der Garbage Collector, der das Chaos reinigt, break, continueund try/ catchAusnahmebehandlung alles den Fall abzudecken , wo gotohilfreich sein könnte, aber in einem sichereren und besser Weise. Javas Popularität beweist, dass man in einer modernen Sprache eine goto-Aussage vermeiden kann.

Zoomen Sie auf die berühmte Sicherheitsanfälligkeit " SSL goto fail"

Wichtiger Haftungsausschluss: Angesichts der heftigen Diskussion in den Kommentaren möchte ich klarstellen, dass ich nicht vorgebe, die goto-Anweisung sei die einzige Ursache für diesen Fehler. Ich behaupte nicht, dass es ohne goto keinen Bug geben würde. Ich möchte nur zeigen, dass ein Goto in einen schwerwiegenden Fehler verwickelt sein kann.

Ich weiß nicht, wie viele schwerwiegende Fehler gotoin der Geschichte der Programmierung vorkommen: Details werden oft nicht mitgeteilt. Es gab jedoch einen berühmten Apple SSL-Fehler , der die Sicherheit von iOS beeinträchtigte. Die Aussage, die zu diesem Fehler führte, war eine falsche gotoAussage.

Einige argumentieren, dass die Hauptursache des Fehlers nicht die goto-Anweisung an sich war, sondern ein falsches Kopieren / Einfügen, ein irreführender Einzug, fehlende geschweifte Klammern um den bedingten Block oder möglicherweise die Arbeitsgewohnheiten des Entwicklers. Ich kann keines von ihnen bestätigen: Alle diese Argumente sind wahrscheinliche Hypothesen und Interpretationen. Niemand weiß es wirklich. (In der Zwischenzeit scheint die in den Kommentaren vorgeschlagene Hypothese einer fehlerhaften Zusammenführung angesichts einiger anderer Inkonsistenzen bei Einrückungen in derselben Funktion ein sehr guter Kandidat zu sein ).

Die einzige objektive Tatsache ist, dass ein Duplikat dazu gotogeführt hat, die Funktion vorzeitig zu verlassen. Betrachtet man den Code, wäre die einzige andere Anweisung, die den gleichen Effekt hätte erzielen können, eine Rückkehr gewesen.

Der Fehler ist SSLEncodeSignedServerKeyExchange()in dieser Datei in Funktion :

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

    ... // Do some cryptographic operations here

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

In der Tat hätten geschweifte Klammern um den bedingten Block den Fehler verhindern können:
Es hätte entweder zu einem Syntaxfehler beim Kompilieren (und damit zu einer Korrektur) oder zu einem redundanten harmlosen goto geführt. Übrigens könnte GCC 6 diese Fehler dank seiner optionalen Warnung zur Erkennung inkonsistenter Einrückungen erkennen.

Aber in erster Linie hätten alle diese Goto mit strukturiertem Code vermieden werden können. Goto ist also zumindest indirekt eine Ursache für diesen Bug. Es gibt mindestens zwei verschiedene Möglichkeiten, um dies zu vermeiden:

Ansatz 1: if-Klausel oder verschachtelte ifs

Anstatt viele Bedingungen nacheinander auf Fehler zu prüfen und failim Problemfall jedes Mal an ein Etikett zu senden , hätte man sich dafür entscheiden können, die kryptografischen Operationen in einer if-Anweisung auszuführen, die dies nur dann tun würde, wenn es keine falsche Vorbedingung gäbe:

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

Ansatz 2: Verwenden Sie einen Fehlerakku

Dieser Ansatz basiert auf der Tatsache, dass fast alle Anweisungen hier eine Funktion aufrufen, um einen errFehlercode festzulegen, und den Rest des Codes nur ausführen, wenn err0 war (dh die Funktion wurde ohne Fehler ausgeführt). Eine schöne sichere und lesbare Alternative ist:

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

Hier gibt es kein einziges Goto: Kein Risiko, schnell zum Ausfallausgangspunkt zu springen. Und visuell wäre es einfach, eine falsch ausgerichtete Linie oder eine vergessene zu erkennen ok &&.

Dieses Konstrukt ist kompakter. Es basiert auf der Tatsache, dass in C der zweite Teil eines logischen und ( &&) nur ausgewertet wird, wenn der erste Teil wahr ist. Tatsächlich entspricht der von einem optimierenden Compiler erzeugte Assembler fast dem ursprünglichen Code mit gotos: Der Optimierer erkennt die Kette von Bedingungen sehr gut und generiert Code, der beim ersten Rückgabewert ungleich Null ans Ende springt ( online proof ).

Sie können sich sogar eine Konsistenzprüfung am Ende der Funktion vorstellen, mit der während der Testphase Unstimmigkeiten zwischen dem OK-Flag und dem Fehlercode festgestellt werden können.

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

Fehler wie ein ==0versehentlich durch einen !=0oder logische Konnektorfehler werden während der Debugging-Phase leicht erkannt.

Wie gesagt: Ich behaupte nicht, dass alternative Konstrukte jeden Fehler vermieden hätten. Ich möchte nur sagen, dass sie das Auftreten des Fehlers erschweren könnten.

Christophe
quelle
34
Nein, der Fehler "Apple goto fail" wurde von einem cleveren Programmierer verursacht, der sich entschied, die geschweiften Klammern nach einer if-Anweisung nicht einzufügen. :-) Als der nächste Wartungsprogrammierer (oder derselbe, derselbe Unterschied) kam und eine weitere Codezeile hinzufügte, die nur ausgeführt werden sollte, wenn die if-Anweisung als wahr ausgewertet wurde, wurde die Anweisung jedes Mal ausgeführt. Bam, der "goto fail" Fehler, der ein großer Sicherheitsfehler war. Aber es hatte im Wesentlichen nichts damit zu tun, dass eine goto-Anweisung verwendet wurde.
Craig
47
Der Apple-Fehler "goto fail" wurde direkt durch eine duplizierte Codezeile verursacht. Der besondere Effekt dieses Fehlers wurde dadurch verursacht, dass diese Zeile in einer Anweisung ohne gotogeschweifte Klammern steht if. Aber wenn Sie vorschlagen, dass das Vermeiden gotoIhren Code gegen die Auswirkungen von zufällig duplizierten Zeilen immun macht, habe ich schlechte Nachrichten für Sie.
immibis
12
Sie verwenden das Verhalten eines booleschen Verknüpfungsoperators, um eine Anweisung über einen Nebeneffekt auszuführen, und behaupten, dies sei klarer als eine ifAnweisung? Lustige Zeiten. :)
Craig
38
Wenn anstelle von "goto fail" die Aussage "failed = true" vorgekommen wäre, wäre genau dasselbe passiert.
gnasher729
15
Dieser Fehler wurde durch einen schlampigen Entwickler verursacht, der nicht darauf achtete, was er oder sie tat, und keine eigenen Codeänderungen las . Nicht mehr, nicht weniger. Okay, vielleicht werfen Sie dort auch ein paar Versehen ein.
Leichtigkeitsrennen im Orbit
34

Der berühmte Artikel von Dijkstra wurde zu einer Zeit geschrieben, als einige Programmiersprachen tatsächlich in der Lage waren, Unterprogramme mit mehreren Ein- und Ausstiegspunkten zu erstellen . Mit anderen Worten, Sie könnten buchstäblich in die Mitte einer Funktion springen und an einer beliebigen Stelle innerhalb der Funktion herausspringen, ohne diese Funktion aufzurufen oder auf herkömmliche Weise zurückzukehren. Das gilt immer noch für die Assemblersprache. Niemand argumentiert jemals, dass ein solcher Ansatz der strukturierten Methode zum Schreiben von Software, die wir heute verwenden, überlegen ist.

In den meisten modernen Programmiersprachen werden Funktionen sehr spezifisch mit einem Einstiegs- und einem Ausstiegspunkt definiert . Am Einstiegspunkt geben Sie die Parameter für die Funktion an und rufen sie auf. Am Ausstiegspunkt geben Sie den resultierenden Wert zurück und setzen die Ausführung gemäß der Anweisung fort, die dem ursprünglichen Funktionsaufruf folgt.

Innerhalb dieser Funktion sollten Sie in der Lage sein, im Rahmen der Vernunft alles zu tun, was Sie wollen. Wenn die Funktion durch ein oder zwei Punkte klarer oder schneller wird, warum nicht? Der springende Punkt einer Funktion ist es, ein bisschen klar definierte Funktionalität zu binden, so dass Sie nicht mehr darüber nachdenken müssen, wie sie intern funktioniert. Sobald es geschrieben ist, benutzt du es einfach.

Und ja, Sie können innerhalb einer Funktion mehrere return-Anweisungen haben. Es gibt immer noch eine Stelle in einer richtigen Funktion, von der aus Sie zurückkehren (im Grunde genommen die Rückseite der Funktion). Das ist keineswegs dasselbe wie das Herausspringen aus einer Funktion mit einem Goto, bevor sie die Chance hat, ordnungsgemäß zurückzukehren.

Bildbeschreibung hier eingeben

Es geht also nicht wirklich um die Verwendung von goto's. Es geht darum, ihren Missbrauch zu vermeiden. Alle sind sich einig, dass Sie mit gotos ein furchtbar kompliziertes Programm erstellen können, aber Sie können dasselbe auch tun, indem Sie Funktionen missbrauchen (es ist einfach viel einfacher, gotos zu missbrauchen).

Seit ich von BASIC-Programmen im Zeilennummern-Stil zu strukturiertem Programmieren mit Pascal und geschweiften Klammern übergegangen bin, musste ich kein Goto mehr verwenden. Das einzige Mal, dass ich versucht war, eine zu verwenden, war das vorzeitige Verlassen einer verschachtelten Schleife (in Sprachen, die kein mehrstufiges vorzeitiges Verlassen von Schleifen unterstützen), aber ich kann normalerweise einen anderen Weg finden, der sauberer ist.

Robert Harvey
quelle
2
Kommentare sind nicht für eine längere Diskussion gedacht. Diese Unterhaltung wurde in den Chat verschoben .
maple_shaft
11

Zu welchen Fehlern führen "goto" -Anweisungen? Gibt es historisch bedeutsame Beispiele?

Ich habe gotoals Kind beim Schreiben von BASIC-Programmen Anweisungen verwendet, um auf einfache Weise For- und While-Schleifen zu erhalten (Commodore 64 BASIC hatte keine While-Schleifen, und ich war zu unreif, um die richtige Syntax und Verwendung von For-Schleifen zu lernen ). Mein Code war häufig trivial, aber alle Schleifenfehler konnten sofort auf meine Verwendung von goto zurückgeführt werden.

Ich verwende jetzt hauptsächlich Python, eine Programmiersprache auf hohem Niveau, die festgestellt hat, dass keine Notwendigkeit für goto besteht.

Als Edsger Dijkstra 1968 erklärte, "Goto als schädlich", gab er nicht eine Handvoll Beispiele an, bei denen verwandte Bugs dem goto angelastet werden könnten, sondern erklärte, dies gotosei für höhere Sprachen unnötig und sollte zugunsten dessen vermieden werden Wir betrachten heute den normalen Kontrollfluss: Schleifen und Bedingungen. Seine Worte:

Die ungezügelte Nutzung des Go-To hat zur Folge, dass es fürchterlich schwierig wird, einen aussagekräftigen Satz von Koordinaten zu finden, in dem der Prozessfortschritt beschrieben werden kann.
[...]
Die aktuelle Go-to- Aussage ist einfach zu primitiv. Es ist zu viel Einladung, sein Programm durcheinander zu bringen.

Er hatte wahrscheinlich Berge von Beispielen von Fehlern, die jedes Mal auftraten, wenn er Code darin debuggte goto. Sein Beitrag war jedoch eine verallgemeinerte Stellungnahme, die durch Beweise gotountermauert war, die für höhere Sprachen nicht erforderlich waren. Der allgemeine Fehler ist, dass Sie möglicherweise nicht in der Lage sind, den fraglichen Code statisch zu analysieren.

Code ist mit gotoAnweisungen viel schwieriger statisch zu analysieren , insbesondere wenn Sie in Ihrem Kontrollfluss (den ich früher durchgeführt habe) oder in einem nicht verwandten Codeabschnitt zurückspringen. Code kann und wurde so geschrieben. Es wurde darauf geachtet, die sehr knappen Rechenressourcen und damit die Architekturen der Maschinen stark zu optimieren.

Ein apokryphales Beispiel

Es gab ein Blackjack-Programm, das ein Betreuer als sehr elegant optimiert empfand, das er jedoch aufgrund der Art des Codes nicht "reparieren" konnte. Es wurde in Maschinencode programmiert, der stark von gotos abhängt - daher halte ich diese Geschichte für ziemlich relevant. Dies ist das beste kanonische Beispiel, das ich mir vorstellen kann.

Ein Gegenbeispiel

Die C-Quelle von CPython (die am häufigsten verwendete und am häufigsten verwendete Python-Referenzimplementierung) verwendet jedoch gotoAnweisungen mit großer Wirkung. Sie werden verwendet, um irrelevante Steuerungsabläufe in Funktionen zu umgehen, um zum Ende der Funktionen zu gelangen und die Funktionen effizienter zu gestalten, ohne die Lesbarkeit zu verlieren. Dies respektiert eines der Ideale der strukturierten Programmierung - einen einzigen Austrittspunkt für Funktionen zu haben.

Ich halte dieses Gegenbeispiel für recht einfach, statisch zu analysieren.

Aaron Hall
quelle
5
Die "Story of Mel", auf die ich gestoßen bin, befand sich in einer seltsamen Architektur, in der (1) jeder Maschinensprachenbefehl nach seiner Operation ein Goto ausführt und (2) es fürchterlich ineffizient war, eine Folge von Befehlen an aufeinanderfolgenden Speicherstellen abzulegen. Und das Programm wurde in handoptimierter Assembly geschrieben, keine kompilierte Sprache.
@MilesRout Ich denke, Sie könnten hier richtig liegen, aber auf der Wikipedia-Seite zur strukturierten Programmierung heißt es: "Die häufigste Abweichung, die in vielen Sprachen vorkommt, ist die Verwendung einer return-Anweisung zum vorzeitigen Verlassen eines Unterprogramms. Dies führt zu mehreren Exit-Punkten anstelle des einzigen Ausstiegspunkts, der für die strukturierte Programmierung erforderlich ist. " - Sie sagen, das ist falsch? Haben Sie eine Quelle zu zitieren?
Aaron Hall
@ AaronHall Das ist nicht die ursprüngliche Bedeutung.
Miles Rout
11

Als das Wigwam aktuelle architektonische Kunst war, konnten ihre Erbauer Ihnen zweifellos fundierte praktische Ratschläge bezüglich des Baus von Wigwams, wie man Rauch entweichen lässt und so weiter geben. Glücklicherweise können die heutigen Bauherren wahrscheinlich die meisten dieser Ratschläge vergessen.

Als die Postkutsche die aktuelle Transportkunst war, konnten ihre Fahrer Ihnen zweifellos fundierte praktische Ratschläge in Bezug auf Pferde von Postkutschen geben, wie man sich gegen Straßenräuber verteidigt , und so weiter. Glücklicherweise können die heutigen Autofahrer auch die meisten dieser Ratschläge vergessen.

Wenn Lochkarten aktuelle Programmierkunst waren, konnten ihre Praktiker Ihnen ebenfalls fundierte praktische Ratschläge zur Organisation von Karten, zur Nummerierung von Aussagen usw. geben. Ich bin mir nicht sicher, ob dieser Rat heute sehr relevant ist.

Bist du alt genug, um die Phrase " Zahlenaussagen " zu kennen ? Sie müssen es nicht wissen, aber wenn Sie es nicht wissen, sind Sie nicht mit dem historischen Kontext vertraut, in dem die Warnung vor gotohauptsächlich relevant war.

Die Warnung davor gotoist heute einfach nicht sehr relevant. Mit dem Grundlagentraining in while / for-Schleifen und Funktionsaufrufen werden Sie nicht einmal daran denken , gotosehr oft eine zu erstellen. Wenn Sie daran denken, haben Sie wahrscheinlich einen Grund, also fahren Sie fort.

Aber kann die gotoAussage nicht missbraucht werden?

Antwort: Sicher, es kann missbraucht werden, aber sein Missbrauch ist ein ziemlich kleines Problem in der Softwareentwicklung im Vergleich zu weitaus häufiger auftretenden Fehlern wie der Verwendung einer Variablen, für die eine Konstante verwendet wird, oder wie dem Ausschneiden und Einfügen (ansonsten) bekannt als Nachlässigkeit gegenüber Refactor). Ich bezweifle, dass Sie in großer Gefahr sind. Wenn Sie die longjmpSteuerung nicht verwenden oder anderweitig auf fernen Code übertragen, können Sie gotosie zum Spaß ausprobieren , wenn Sie die Verwendung von a oder möchten. Es wird Dir gut gehen.

Sie können das Fehlen der letzten Horrorgeschichten bemerken, in denen gotoder Bösewicht spielt. Die meisten oder alle dieser Geschichten scheinen 30 oder 40 Jahre alt zu sein. Sie stehen auf festem Grund, wenn Sie diese Geschichten als größtenteils veraltet ansehen.

thb
quelle
1
Ich sehe, dass ich mindestens eine Gegenstimme angezogen habe. Nun, das ist zu erwarten. Weitere Abstimmungen können kommen. Ich werde jedoch nicht die konventionelle Antwort geben, wenn mich jahrzehntelange Programmiererfahrung gelehrt hat, dass in diesem Fall die konventionelle Antwort falsch ist. Dies ist jedenfalls meine Ansicht. Ich habe meine Gründe angegeben. Der Leser kann entscheiden.
25.
1
Dein Beitrag ist gut oder schlecht. Ich kann keine Kommentare ablehnen.
Pieter B
7

Um eine Sache zu den anderen ausgezeichneten Antworten hinzuzufügen, kann es mit gotoAussagen schwierig sein, genau zu sagen, wie Sie an eine bestimmte Stelle im Programm gelangt sind. Sie können wissen, dass in einer bestimmten Zeile eine Ausnahme aufgetreten ist, aber wenn sich gotoim Code eine Ausnahme befindet, können Sie nicht feststellen, welche Anweisungen ausgeführt wurden, um diesen Ausnahmezustand zu verursachen, ohne das gesamte Programm zu durchsuchen. Es gibt keinen Aufrufstapel und keinen visuellen Fluss. Es ist möglich, dass es eine Anweisung gibt, die 1000 Zeilen entfernt ist und Sie in einen schlechten Zustand versetzt, indem eine Anweisung gotoan die Zeile ausgeführt wird, die eine Ausnahme ausgelöst hat.

qfwfq
quelle
1
Visual Basic 6 und VBA haben schmerzhafte Fehlerbehandlung, aber wenn Sie verwenden On Error Goto errh, können Sie erneut Resumeversuchen, Resume Nextdie fehlerhafte Zeile überspringen und Erlwissen, um welche Zeile es sich handelt (wenn Sie Zeilennummern verwenden).
Cees Timmerman
@CeesTimmerman Ich habe sehr wenig mit Visual Basic gemacht, das wusste ich nicht. Ich werde einen Kommentar dazu hinzufügen.
qfwfq
2
@CeesTimmerman: Die VB6-Semantik von "resume" ist fürchterlich, wenn ein Fehler in einer Subroutine auftritt, die keinen eigenen On-Error-Block hat. ResumeFührt diese Funktion von Anfang an erneut aus und Resume Nextüberspringt alles in der Funktion, was noch nicht vorhanden ist.
Supercat
2
@supercat Wie ich schon sagte, es ist schmerzhaft. Wenn Sie die genaue Zeile kennen müssen, sollten Sie sie alle nummerieren oder die Fehlerbehandlung mit On Error Goto 0und manuelles Debuggen vorübergehend deaktivieren . Resumeist dafür vorgesehen, nach Überprüfung Err.Numberund notwendigen Einstellungen verwendet zu werden.
Cees Timmerman
1
Ich sollte beachten, dass in einer modernen Sprache gotonur mit beschrifteten Linien gearbeitet wird, die anscheinend von beschrifteten Schleifen abgelöst wurden .
Cees Timmerman
7

goto ist für Menschen schwieriger zu verstehen als andere Formen der Flusskontrolle.

Das Programmieren des richtigen Codes ist schwierig. Richtige Programme zu schreiben ist schwierig, festzustellen, ob Programme korrekt sind, ist schwierig, zu beweisen, dass Programme korrekt sind.

Es ist einfach, Code dazu zu bringen, vage das zu tun, was Sie wollen, verglichen mit allem anderen, was mit Programmieren zu tun hat.

goto löst einige Programme, indem Code abgerufen wird, um das zu tun, was Sie wollen. Dies erleichtert nicht die Überprüfung der Richtigkeit, während dies bei Alternativen häufig der Fall ist.

Es gibt Programmierstile, bei denen goto die geeignete Lösung ist. Es gibt sogar Programmierstile, bei denen der böse Zwilling comefrom die geeignete Lösung ist. In beiden Fällen ist äußerste Sorgfalt geboten, um sicherzustellen, dass Sie es in einem verständlichen Muster verwenden, und es muss manuell überprüft werden, ob es nicht schwierig ist, die Richtigkeit sicherzustellen.

Als Beispiel gibt es eine Eigenschaft einiger Sprachen, die Coroutinen genannt werden. Coroutinen sind fadenlose Fäden; Ausführungsstatus ohne Thread, auf dem es ausgeführt werden soll. Sie können sie zur Ausführung auffordern, und sie können einen Teil von sich selbst ausführen und sich dann aussetzen und die Flusskontrolle zurückgeben.

"Flache" Coroutinen in Sprachen ohne Coroutine-Unterstützung (wie C ++ vor C ++ 20 und C) sind mit einer Mischung aus gotos und manueller Zustandsverwaltung möglich. "Tiefe" Coroutinen können mit den setjmpund longjmpFunktionen von C erstellt werden.

Es gibt Fälle, in denen Coroutinen so nützlich sind, dass es sich lohnt, sie manuell und sorgfältig zu schreiben.

Im Fall von C ++ werden sie als so nützlich befunden, dass sie die Sprache erweitern, um sie zu unterstützen. Das gotos und manuelle Zustandsmanagement ist hinter einer Null-Kosten-Abstraktionsschicht verborgen, die es Programmierern ermöglicht, sie zu schreiben, ohne die Schwierigkeit zu haben, zu beweisen, dass das Durcheinander von goto, void**s, manuellem Aufbau / Zerstörung des Zustands usw. korrekt ist.

Das Goto verbirgt sich hinter einer übergeordneten Abstraktion, wie whileoder foroder ifoder switch. Diese übergeordneten Abstraktionen lassen sich leichter als richtig erweisen und überprüfen.

Wenn in der Sprache einige fehlten (wie in einigen modernen Sprachen Koroutinen fehlen), wird entweder das Limpling zusammen mit einem Muster, das nicht zum Problem passt, oder die Verwendung von goto zu Ihrer Alternative.

Es ist einfach, einen Computer dazu zu bringen, vage das zu tun, was Sie möchten . Zuverlässig robusten Code zu schreiben ist schwierig . Springen hilft dem Ersten weitaus mehr als dem Zweiten; Daher wird es als schädlich eingestuft, da es ein Zeichen für oberflächlich "arbeitenden" Code mit tiefen und schwer auffindbaren Fehlern ist. Mit ausreichendem Aufwand kann man Code mit "goto" zuverlässig robust machen, so dass es falsch ist, die Regel als absolut zu halten; aber als Faustregel ist es eine gute.

Yakk
quelle
1
longjmpIn C ist es nur zulässig, den Stapel setjmpin eine übergeordnete Funktion zurückzuspulen. (Wie das Ausbrechen aus mehreren Verschachtelungsebenen, aber für Funktionsaufrufe anstelle von Schleifen). longjjmpEs setjmpist nicht legal , einen Kontext mit einer Funktion zu erstellen, die seitdem zurückgegeben wurde (und ich vermute, dass dies in den meisten Fällen in der Praxis nicht funktioniert). Ich bin nicht mit Co-Routinen vertraut, aber aus Ihrer Beschreibung einer Co-Routine, die einem User-Space-Thread ähnelt, gehe ich davon aus, dass sie einen eigenen Stack sowie einen Kontext für gespeicherte Register benötigt und longjmpnur ein Register enthält -sparend, kein separater Stapel.
Peter Cordes
1
Oh, ich hätte einfach googeln sollen: fanf.livejournal.com/105413.html beschreibt, wie Stapelspeicher für die Coroutine zum Laufen gebracht werden. (Was, wie ich vermutet habe, zusätzlich zur Verwendung von setjmp / longjmp erforderlich ist).
Peter Cordes
2
@PeterCordes: Es sind keine Implementierungen erforderlich, um Mittel bereitzustellen, mit denen Code ein Paar jmp_buff erstellen kann, das zur Verwendung als Coroutine geeignet ist. Einige Implementierungen geben ein Mittel an, mit dem Code ein solches erstellen kann, obwohl der Standard dies nicht vorschreibt. Ich würde keinen Code beschreiben, der auf solchen Techniken wie "Standard C" beruht, da in vielen Fällen ein jmp_buff eine undurchsichtige Struktur ist, die sich nicht garantiert konsistent zwischen verschiedenen Compilern verhält.
Supercat
6

Werfen Sie einen Blick auf diesen Code, von http://www-personal.umich.edu/~axe/research/Software/CC/CC2/TourExec1.1.f.html , die tatsächlich Teil eines großen Gefangenendilemma Simulation war. Wenn Sie alten FORTRAN- oder BASIC-Code gesehen haben, werden Sie feststellen, dass dies nicht ungewöhnlich ist.

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

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

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

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

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

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

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

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

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

75 IRAND = 2
   ICOOP = 2
   K75R = 0
   RETURN

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

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

Hier gibt es viele Probleme, die weit über die GOTO-Aussage hinausgehen. Ich denke ehrlich, die GOTO-Aussage war ein bisschen ein Sündenbock. Der Kontrollfluss ist hier jedoch absolut nicht klar, und der Code ist so gemischt, dass sehr unklar ist, was vor sich geht. Auch ohne das Hinzufügen von Kommentaren oder die Verwendung besserer Variablennamen würde eine Änderung in eine Blockstruktur ohne GOTOs das Lesen und Verfolgen erheblich erleichtern.

prosfilaes
quelle
4
so wahr :-) Kann erwähnenswert sein: Legacy-FORTAN und BASIC hatten keine Blockstrukturen, so dass GOTO die einzige Alternative war, wenn eine THEN-Klausel nicht in einer Zeile festgehalten werden konnte.
Christophe
Textbeschriftungen anstelle von Zahlen oder zumindest Kommentare zu den nummerierten Zeilen wären sehr hilfreich.
Cees Timmerman
Zurück in meinen FORTRAN-IV-Tagen: Ich habe die Verwendung von GOTO eingeschränkt, um IF / ELSE-IF / ELSE-Blöcke, WHILE-Schleifen und BREAK- und NEXT-In-Schleifen zu implementieren. Jemand hat mir die Übel des Spaghetti-Codes gezeigt und warum Sie strukturieren sollten, um dies zu vermeiden, selbst wenn Sie Ihre Blockstrukturen mit GOTO implementieren müssen. Später habe ich angefangen, einen Pre-Prozessor (RATFOR, IIRC) zu verwenden. Goto ist an sich nicht böse, es ist nur ein übermächtiges Konstrukt auf niedriger Ebene, das einer Assembler-Verzweigungsanweisung zugeordnet ist. Wenn Sie es verwenden müssen, weil Ihre Sprache unzulänglich ist, verwenden Sie es, um die richtigen Blockstrukturen zu erstellen oder zu erweitern.
Nigel222
@ CeesTimmerman: Das ist der FORTRAN IV-Code der Gartensorte. Textbeschriftungen wurden in FORTRAN IV nicht unterstützt. Ich weiß nicht, ob die neueren Versionen der Sprache sie unterstützen. Kommentare in derselben Zeile wie Code wurden in FORTRAN IV nicht unterstützt, obwohl möglicherweise herstellerspezifische Erweiterungen vorhanden waren, um sie zu unterstützen. Ich weiß nicht, was der "aktuelle" FORTRAN-Standard besagt: Ich habe FORTRAN vor langer Zeit verlassen, und ich vermisse es nicht. (Der neueste FORTRAN-Code, den ich gesehen habe, hat eine starke Ähnlichkeit mit PASCAL aus den frühen 1970ern.)
John R. Strohm,
1
@CeesTimmerman: Herstellerspezifische Erweiterung. Der Code im Allgemeinen ist sehr schüchtern gegenüber Kommentaren. Es gab auch eine Konvention, die an einigen Stellen üblich wurde, Zeilennummern nur in CONTINUE- und FORMAT-Anweisungen einzufügen, um das spätere Hinzufügen von Zeilen zu vereinfachen. Dieser Code entspricht nicht dieser Konvention.
John R. Strohm
0

Eines der Prinzipien der wartbaren Programmierung ist die Kapselung . Der Punkt ist, dass Sie eine Schnittstelle mit einem Modul / einer Routine / einer Unterroutine / einer Komponente / einem Objekt unter Verwendung einer definierten Schnittstelle und nur dieser Schnittstelle herstellen und die Ergebnisse vorhersehbar sind (vorausgesetzt, die Einheit wurde effektiv getestet).

Innerhalb einer einzigen Codeeinheit gilt das gleiche Prinzip. Wenn Sie konsequent strukturierte Programmierung oder objektorientierte Programmierprinzipien anwenden, werden Sie nicht:

  1. Erstellen Sie unerwartete Pfade durch den Code
  2. kommen in Codeabschnitten mit undefinierten oder unzulässigen Variablenwerten an
  3. Verlassen Sie einen Pfad des Codes mit undefinierten oder unzulässigen Variablenwerten
  4. Eine Transaktionseinheit kann nicht abgeschlossen werden
  5. Lassen Sie Teile des Codes oder der Daten im Arbeitsspeicher, die tatsächlich tot sind, aber nicht für die Bereinigung und Neuzuweisung von Müll geeignet sind, da Sie ihre Freigabe nicht mit einem expliziten Konstrukt signalisiert haben, das die Freigabe aufruft, wenn der Codesteuerungspfad die Einheit verlässt

Zu den häufigsten Symptomen dieser Verarbeitungsfehler gehören Speicherverluste, Speicherhorting, Zeigerüberläufe, Abstürze, unvollständige Datensätze, Ausnahmen beim Hinzufügen / Ändern / Löschen von Datensätzen, Speicherseitenfehler usw.

Die vom Benutzer beobachtbaren Manifestationen dieser Probleme umfassen die Sperrung der Benutzeroberfläche, eine allmählich abnehmende Leistung, unvollständige Datensätze, die Unfähigkeit, Transaktionen zu initiieren oder abzuschließen, beschädigte Daten, Netzwerkausfälle, Stromausfälle und Ausfälle eingebetteter Systeme (aufgrund des Verlustes der Kontrolle über Raketen) auf den Verlust der Verfolgungs- und Kontrollfähigkeit in Flugsicherungssystemen), Zeitgeberausfälle und so weiter und so fort. Der Katalog ist sehr umfangreich.

Jaxter
quelle
3
Sie antworteten, ohne es gotoeinmal zu erwähnen . :)
Robert Harvey
0

goto ist gefährlich, weil es normalerweise dort verwendet wird, wo es nicht benötigt wird. Alles, was nicht benötigt wird, ist gefährlich, aber besonders wichtig . Wenn Sie googeln, werden Sie viele Fehler finden, die durch goto verursacht werden. Dies ist per se kein Grund, es nicht zu verwenden (Fehler treten immer auf, wenn Sie Sprachfunktionen verwenden, da dies für die Programmierung von wesentlicher Bedeutung ist), aber einige davon hängen eindeutig eng zusammen zu goto Nutzung.


Gründe für die Verwendung / Nichtverwendung von goto :

  • Wenn Sie eine Schleife benötigen, müssen Sie whileoder verwenden for.

  • Wenn Sie einen bedingten Sprung ausführen müssen, verwenden Sie if/then/else

  • Wenn Sie eine Prozedur benötigen, rufen Sie eine Funktion / Methode auf.

  • Wenn Sie eine Funktion beenden müssen, genügt es return.

Ich kann an meinen Fingern die Stellen zählen, an denen ich sie gesehen gotohabe und richtig benutzt habe.

  • CPython
  • libKTX
  • wahrscheinlich noch ein paar mehr

In libKTX gibt es eine Funktion mit dem folgenden Code

if(something)
    goto cleanup;

if(bla)
    goto cleanup;

cleanup:
    delete [] array;

Jetzt an dieser Stelle gotoist nützlich, weil die Sprache C ist:

  • Wir sind in einer Funktion
  • Wir können keine Bereinigungsfunktion schreiben (da wir in einen anderen Bereich eintreten und das Bereitstellen eines barrierefreien Aufruferfunktionsstatus eine größere Belastung darstellt).

Dieser Anwendungsfall ist nützlich, da in C keine Klassen vorhanden sind. Die einfachste Methode zum Bereinigen ist daher die Verwendung von a goto.

Wenn wir den gleichen Code in C ++ hatten, brauchen wir nicht mehr zu gehen:

class MyClass{
    public:

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

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

    }



}

Zu was für Fehlern kann es führen? Etwas. Endlosschleifen, falsche Reihenfolge der Ausführung, Stapelschraube ..

Ein dokumentierter Fall ist eine Sicherheitsanfälligkeit in SSL, die es Man in the middle-Angriffen ermöglicht, die durch die falsche Verwendung von goto verursacht werden. Hier ist der Artikel

Dies ist ein Tippfehler, der jedoch eine Zeit lang unbemerkt blieb. Wenn der Code auf andere Weise strukturiert wäre und ordnungsgemäß getestet worden wäre, wäre ein solcher Fehler möglicherweise nicht möglich gewesen.

Spielentwickler
quelle
2
Zu einer Antwort gibt es viele Kommentare, die sehr deutlich erklären, dass die Verwendung von "goto" für den SSL-Fehler völlig irrelevant war - der Fehler wurde durch unachtsame Verdopplung einer Codezeile verursacht, sodass er einmal bedingt und einmal bedingungslos ausgeführt wurde.
gnasher729
Ja, ich warte auf ein besseres historisches Beispiel für einen Goto-Bug, bevor ich akzeptiere. Wie gesagt; Es wäre wirklich egal, was in dieser Codezeile steht. Das Fehlen von geschweiften Klammern hätte es trotzdem ausgeführt und einen Fehler verursacht. Ich bin allerdings neugierig; Was ist eine "Stapelschraube"?
Akiva
1
Ein Beispiel aus Code, an dem ich zuvor in PL / 1 CICS gearbeitet habe. Programm löschte einen Bildschirm bestehend aus einzeiligen Karten. Als der Bildschirm zurückgegeben wurde, fand er die gesendeten Karten. Verwendete die Elemente davon, um den Index in einem Array zu finden, und verwendete dann diesen Index für einen Index von Arrayvariablen, um ein GOTO auszuführen, damit diese einzelne Zuordnung verarbeitet werden konnte. Wenn das gesendete Array von Maps falsch war (oder beschädigt wurde), konnte versucht werden, ein GOTO für das falsche Label auszuführen, oder es konnte zum Absturz gebracht werden, da auf ein Element nach dem Ende des Arrays von Label-Variablen zugegriffen wurde. Umgeschrieben, um die Falltypsyntax zu verwenden.
Kickstart