Ich zögere wirklich, dies zu fragen, weil ich nicht "Debatten, Argumente, Umfragen oder erweiterte Diskussionen anregen" möchte, aber ich bin neu in C und möchte mehr Einsicht in die in der Sprache verwendeten Muster gewinnen.
Ich habe kürzlich eine gewisse Abneigung gegen den goto
Befehl gehört, aber ich habe auch kürzlich einen anständigen Anwendungsfall dafür gefunden.
Code wie folgt:
error = function_that_could_fail_1();
if (!error) {
error = function_that_could_fail_2();
if (!error) {
error = function_that_could_fail_3();
...to the n-th tab level!
} else {
// deal with error, clean up, and return error code
}
} else {
// deal with error, clean up, and return error code
}
Wenn der Aufräum-Teil alles sehr ähnlich ist, könnte etwas hübscher (meiner Meinung nach?) Geschrieben werden:
error = function_that_could_fail_1();
if(error) {
goto cleanup;
}
error = function_that_could_fail_2();
if(error) {
goto cleanup;
}
error = function_that_could_fail_3();
if(error) {
goto cleanup;
}
...
cleanup:
// deal with error if it exists, clean up
// return error code
Ist dies ein gängiger oder akzeptabler Anwendungsfall von goto
in C? Gibt es einen anderen / besseren Weg, dies zu tun?
c
design-patterns
goto
Robz
quelle
quelle
goto hell;
Antworten:
Die
goto
Anweisung (und seine entsprechenden Etiketten) sind ein Stromregel primitiver (zusammen mit bedingter Ausführung einer Anweisung). Damit meine ich, dass sie Ihnen den Aufbau von Netzwerken zur Programmflusssteuerung ermöglichen. Sie können sich diese als Modellierung der Pfeile zwischen den Knoten eines Flussdiagramms vorstellen.Einige davon können sofort optimiert werden, wenn ein direkter linearer Fluss vorliegt (Sie verwenden nur eine Folge grundlegender Anweisungen). Andere Muster werden am besten durch strukturierte Programmierkonstrukte ersetzt, sofern diese verfügbar sind. Wenn es wie eine
while
Schleife aussieht , verwenden Sie einewhile
Schleife , OK? Die strukturierten Programmiermuster sind definitiv zumindest potenziell eindeutiger als ein Durcheinander vongoto
Aussagen.C enthält jedoch nicht alle möglichen strukturierten Programmierkonstrukte. (Es ist mir nicht klar, dass alle relevanten bereits entdeckt wurden. Die Entdeckungsrate ist jetzt langsam, aber ich würde zögern, zu sagen, dass alle gefunden wurden.) Von denen, die wir kennen, fehlt C definitiv die
try
/catch
/finally
Struktur (und Ausnahmen auch). Es fehlt auch Multi-Level-break
From-Loop. Dies sind die Arten von Dingen, mit denengoto
man etwas implementieren kann. Es ist möglich, auch andere Schemata zu verwenden - wir wissen, dass C eine ausreichende Menge vongoto
Primitive - aber diese beinhalten oft das Erstellen von Flag-Variablen und viel komplexeren Schleifen- oder Guard-Bedingungen; Durch eine stärkere Verknüpfung der Kontrollanalyse mit der Datenanalyse wird das Programm insgesamt schwerer verständlich. Dies erschwert auch die Optimierung des Compilers und die schnelle Ausführung der CPU (die meisten Flusssteuerungskonstrukte sind - und definitivgoto
- sehr billig).Daher sollten Sie nicht verwenden, es
goto
sei denn, Sie sollten sich bewusst sein, dass es vorhanden ist und dass es möglicherweise benötigt wird, und wenn Sie es benötigen, sollten Sie sich nicht zu schlecht fühlen. Ein Beispiel für einen Fall, in dem dies erforderlich ist, ist die Freigabe der Ressource, wenn eine aufgerufene Funktion eine Fehlerbedingung zurückgibt. (Das heißt,try
/finally
.) Es ist möglich, das zu schreiben, ohnegoto
es zu tun, aber das kann Nachteile für sich haben, wie zum Beispiel die Probleme, es zu warten. Ein Beispiel für den Fall:Der Code könnte noch kürzer sein, aber es reicht aus, um den Punkt zu demonstrieren.
quelle
try/catch/finally
,goto
trotz der ähnlichen und dennoch weit verbreiteten Form von Spaghetti-Code (da dieser sich über mehrere Funktionen / Module erstrecken kann), die mit Spaghetti-Code möglich isttry/catch/finally
?Ja.
Es wird zum Beispiel im Linux-Kernel verwendet. Hier ist eine E-Mail vom Ende eines Threads von vor fast einem Jahrzehnt , der meinen aufdringlich macht:
Das heißt, es erfordert viel Disziplin, sich von der Erstellung von Spaghetti-Code abzuhalten, sobald Sie sich an die Verwendung von goto gewöhnt haben. Wenn Sie also nicht etwas schreiben, das Geschwindigkeit und geringen Speicherbedarf erfordert (wie einen Kernel oder ein eingebettetes System), sollten Sie das wirklich tun Denken Sie darüber nach, bevor Sie das erste Goto schreiben.
quelle
Meiner Meinung nach ist der von Ihnen veröffentlichte Code ein Beispiel für eine gültige Verwendung von
goto
, da Sie nur nach unten springen und ihn nur wie einen primitiven Ausnahmebehandler verwenden.Aufgrund der alten goto-Debatte meiden Programmierer jedoch seit
goto
rund 40 Jahren und sind daher nicht daran gewöhnt, Code mit goto zu lesen. Dies ist ein triftiger Grund, um Goto zu vermeiden: Es ist einfach nicht der Standard.Ich hätte den Code so umgeschrieben, dass er von C-Programmierern leichter gelesen werden kann:
Vorteile dieses Designs:
quelle
Ein berühmtes Papier, das den Fall einer gültigen Verwendung von beschreibt, war Structured Programming with GOTO Statement von Donald E. Knuth (Stanford University). Das Papier erschien in den Tagen, in denen die Verwendung von GOTO von einigen als Sünde angesehen wurde und als die Bewegung für strukturierte Programmierung ihren Höhepunkt erreichte. Vielleicht möchten Sie einen Blick auf GoTo als schädlich werfen .
quelle
In Java würden Sie das so machen:
Ich benutze das viel. So sehr ich es auch nicht mag
goto
, in den meisten anderen Sprachen im C-Stil verwende ich Ihren Code. Es gibt keinen anderen guten Weg, dies zu tun. (Das Herausspringen aus verschachtelten Schleifen ist ein ähnlicher Fall. In Java verwende ich eine beschriftetebreak
und in allen anderen Fällen einegoto
.)quelle
Ich denke, es ist ein anständiger Anwendungsfall, aber wenn "error" nichts anderes als ein boolescher Wert ist, gibt es einen anderen Weg, um das zu erreichen, was Sie wollen:
Dies nutzt die Kurzschlussauswertung von Booleschen Operatoren. Wenn dies "besser" ist, hängt es von Ihrem persönlichen Geschmack ab und wie Sie an diese Redewendung gewöhnt sind.
quelle
error
der Wert mit der gesamten ODER-Verknüpfung bedeutungslos werden kann.Der Linux Style Guide gibt spezifische Gründe für die Verwendung von
goto
's, die Ihrem Beispiel entsprechen:https://www.kernel.org/doc/Documentation/process/coding-style.rst
Haftungsausschluss Ich soll meine Arbeit nicht teilen. Die Beispiele hier sind ein bisschen erfunden, bitte tragen Sie mit mir.
Dies ist gut für die Speicherverwaltung. Ich habe kürzlich an Code gearbeitet, der dynamisch zugewiesenen Speicher hatte (zum Beispiel einen
char *
von einer Funktion zurückgegebenen). Eine Funktion, die einen Pfad untersucht und feststellt, ob der Pfad gültig ist, indem die Token des Pfads analysiert werden:Für mich ist der folgende Code viel netter und einfacher zu pflegen, wenn Sie Folgendes hinzufügen müssen
varNplus1
:Jetzt hatte der Code alle möglichen anderen Probleme, nämlich dass N irgendwo über 10 lag und die Funktion über 450 Zeilen mit 10 Verschachtelungsebenen an einigen Stellen.
Aber ich habe meinem Vorgesetzten angeboten, es umzugestalten, was ich auch getan habe, und jetzt gibt es eine Reihe von Funktionen, die alle kurz sind und alle den Linux-Stil haben
Wenn wir das Äquivalent ohne
goto
s betrachten:Für mich ist es im ersten Fall offensichtlich, dass
NULL
wir hier raus und zurückkehren , wenn die erste Funktion zurückkehrt0
. Im zweiten Fall muss ich nach unten scrollen, um zu sehen, dass das if die gesamte Funktion enthält. Zugegeben, der erste zeigt mir dies stilistisch an (der Name "out
") und der zweite macht es syntaktisch. Der erste ist noch offensichtlicher.Außerdem bevorzuge ich es sehr,
free()
Anweisungen am Ende einer Funktion zu haben. Dies liegt meiner Erfahrung nach zum Teil daran, dassfree()
Aussagen in der Mitte von Funktionen schlecht riechen und mir signalisieren, dass ich ein Unterprogramm erstellen sollte. In diesem Fall habe ichvar1
in meiner Funktion erstellt und konnte es nichtfree()
in einem Unterprogramm, aber das ist der Grundgoto out_free
, warum der Goto-Out-Stil so praktisch ist.Ich denke, Programmierer müssen in dem Glauben erzogen werden, dass
goto
es böse ist. Wenn sie dann ausgereift sind, sollten sie den Linux-Quellcode durchsuchen und den Linux-Styleguide lesen.Ich sollte hinzufügen, dass ich diesen Stil sehr konsequent verwende, jede Funktion hat ein int
retval
, einout_free
label und ein out label. Aufgrund der stilistischen Konsistenz wird die Lesbarkeit verbessert.Bonus: Bricht und geht weiter
Angenommen, Sie haben eine while-Schleife
An diesem Code sind andere Dinge falsch, aber eine Sache ist die continue-Anweisung. Ich würde das Ganze gerne umschreiben, hatte aber den Auftrag, es ein wenig zu modifizieren. Ich hätte Tage gebraucht, um es auf eine Weise umzugestalten, die mich zufriedenstellte, aber die eigentliche Änderung war ungefähr ein halber Tag Arbeit. Das Problem ist, dass wir, auch wenn wir "
continue
" noch freivar1
und brauchenvar2
. Ich musste ein hinzufügenvar3
, und es brachte mich dazu, kotzen zu müssen, um die free () -Anweisungen zu spiegeln.Ich war zu der Zeit ein relativ neuer Praktikant, aber ich hatte vor einiger Zeit zum Spaß den Linux-Quellcode angesehen und meinen Vorgesetzten gefragt, ob ich eine goto-Anweisung verwenden könnte. Er sagte ja und ich tat dies:
Ich denke, die Fortsetzung ist im besten Fall in Ordnung, aber für mich sind sie wie ein Goto mit einem unsichtbaren Etikett. Das gleiche gilt für Pausen. Ich würde es immer noch vorziehen, fortzufahren oder abzubrechen, es sei denn, wie hier, es zwingt Sie, Änderungen an mehreren Stellen zu spiegeln.
Und ich sollte hinzufügen, dass diese Verwendung von
goto next;
und dasnext:
Etikett für mich unbefriedigend sind. Sie sind lediglich besser als das Spiegelnfree()
dercount++
Aussagen und Aussagen.goto
sind fast immer falsch, aber man muss wissen, wann sie gut zu gebrauchen sind.Eine Sache, die ich nicht besprochen habe, ist die Fehlerbehandlung, die in anderen Antworten behandelt wurde.
Performance
Man kann sich die Implementierung von strtok () ansehen: http://opensource.apple.com//source/Libc/Libc-167/string.subproj/strtok.c
Bitte korrigieren Sie mich, wenn ich falsch liege, aber ich glaube, dass das
cont:
Label und diegoto cont;
Aussage der Leistung dienen (sie machen den Code mit Sicherheit nicht besser lesbar). Sie könnten durch lesbaren Code ersetzt werdenBegrenzer zu überspringen. Um jedoch so schnell wie möglich zu sein und unnötige Funktionsaufrufe zu vermeiden, tun sie dies auf diese Weise.
Ich habe die Zeitung von Dijkstra gelesen und finde sie ziemlich esoterisch.
google "dijkstra goto statement" als schädlich eingestuft, da ich nicht genug Reputation habe, um mehr als 2 Links zu posten.
Ich habe gesehen, dass es als ein Grund angeführt wurde, goto's nicht zu verwenden, und dass das Lesen nichts daran geändert hat, was meine Verwendung von goto's angeht.
Nachtrag :
Ich habe mir eine ordentliche Regel ausgedacht, während ich darüber nachdachte, wie es weitergeht und wie es weitergeht.
Aufgrund von Umfangsproblemen ist dies nicht immer möglich, aber ich habe festgestellt, dass es dadurch viel einfacher ist, über meinen Code nachzudenken. Mir war aufgefallen, dass es mir immer ein schlechtes Gefühl gab, wenn eine while-Schleife eine Pause oder ein Fortsetzen hatte.
quelle
Persönlich würde ich es eher so umgestalten:
Das wäre motivierter, wenn man das Deep Nesting vermeidet, als wenn man das Goto vermeidet (IMO ein schlimmeres Problem mit dem ersten Codebeispiel), und wäre natürlich davon abhängig, dass CleanupAfterError außerhalb des Geltungsbereichs möglich ist (in diesem Fall "params") Eine Struktur, die einen zugewiesenen Speicher enthält, den Sie freigeben müssen, eine Datei *, die Sie schließen müssen, oder was auch immer.
Ein wesentlicher Vorteil, den ich bei diesem Ansatz sehe, ist, dass es sowohl einfacher als auch sauberer ist, einen hypothetischen zukünftigen zusätzlichen Schritt zwischen beispielsweise FTCF2 und FTCF3 einzufügen (oder einen vorhandenen aktuellen Schritt zu entfernen), sodass sich die Wartbarkeit (und die Person, die dies tut) verbessern erbt meinen Code, will mich nicht lynchen!) - beiseite, die verschachtelte Version hat das nicht.
quelle
Sehen Sie sich die C-Codierungsrichtlinien der MISRA (Motor Industry Software Reliability Association) an, die es ermöglichen, unter strengen Kriterien zu arbeiten (die Ihr Beispiel erfüllt).
Wo ich arbeite, wird derselbe Code geschrieben - es ist nicht erforderlich - unnötige religiöse Debatten über sie zu vermeiden, ist ein großes Plus in jedem Softwarehaus.
oder für "goto in drag" - etwas noch zwielichtigeres als goto, aber umgeht das "no goto ever !!!" camp) "Sicherlich muss es OK sein, verwendet nicht Springen" ....
Wenn die Funktionen denselben Parametertyp haben, legen Sie sie in eine Tabelle und verwenden Sie eine Schleife.
quelle
if(flag)
Kopie den "if" -Zweig und jede entsprechende Anweisung in der anderen Kopie den "if" -Zweig enthält. sonst". Aktionen, die das Flag setzen und löschen, sind wirklich "gotos", die zwischen diesen beiden Versionen des Codes springen. Es gibt Zeiten, in denen die Verwendung von Flags sauberer ist als jede andere Alternative. Das Hinzufügen einer Flagge zum Speichern einesgoto
Ziels ist jedoch kein guter Kompromiss.Ich benutze auch,
goto
wenn das alternativedo/while/continue/break
Hackery weniger lesbar wäre.goto
s haben den Vorteil, dass ihre Ziele einen Namen haben und sie lesengoto something;
. Dies kann besser lesbar alsbreak
odercontinue
wenn Sie nicht wirklich stoppen oder etwas fortsetzt.quelle
do ... while(0)
oder eines anderen Konstrukts, bei dem es sich nicht um eine tatsächliche Schleife handelt, sondern um einen systematischen Versuch, die Verwendung von a zu verhinderngoto
.quelle
break
funktioniert das genausogoto
, obwohl es kein Stigma trägt.Es wird immer Lager geben, die sagen, dass ein Weg akzeptabel ist und ein anderer nicht. Unternehmen, für die ich gearbeitet habe, haben die Stirn gerunzelt oder von der Verwendung dringend abgeraten. Persönlich kann ich mir keine Zeit vorstellen, in der ich eine verwendet habe, aber das bedeutet nicht, dass sie schlecht sind, sondern nur eine andere Art, Dinge zu tun.
In C mache ich normalerweise Folgendes:
Für die Verarbeitung würde ich anhand Ihres goto-Beispiels Folgendes tun:
error = function_that_could_fail_1 (); if (! error) {error = function_that_could_fail_2 (); } if (! error) {error = function_that_could_fail_3 (); }
Es gibt keine Verschachtelung, und innerhalb der if-Klauseln können Sie Fehlerberichte erstellen, wenn der Schritt einen Fehler generiert hat. Es muss also nicht "schlimmer" sein als eine Methode mit gotos.
Ich bin noch nicht auf einen Fall gestoßen, in dem jemand Dinge hat, die mit einer anderen Methode nicht gemacht werden können und die genauso lesbar / verständlich sind, und das ist der Schlüssel, IMHO.
quelle