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
Das Grundprinzip für die Verwendung von gotos ist:
- Unbedingte Aussagen sind leichter zu verstehen und zu befolgen
- Verschachtelung wird reduziert
- fehler durch nicht aktualisierung einzelner ausgangspunkte bei änderungen werden verhindert
- spart dem Compiler Arbeit, um redundanten Code weg zu optimieren;)
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:
tmp_string = strdup(string);
token = strtok(tmp_string,delim);
while( token != NULL ){
...
some statements, some involving dynamically allocated memory
...
if ( check_this() ){
free(var1);
free(var2);
...
free(varN);
return 1;
}
...
some more stuff
...
if(something()){
if ( check_that() ){
free(var1);
free(var2);
...
free(varN);
return 1;
} else {
free(var1);
free(var2);
...
free(varN);
return 0;
}
}
token = strtok(NULL,delim);
}
free(var1);
free(var2);
...
free(varN);
return 1;
Für mich ist der folgende Code viel netter und einfacher zu pflegen, wenn Sie Folgendes hinzufügen müssen varNplus1
:
int retval = 1;
tmp_string = strdup(string);
token = strtok(tmp_string,delim);
while( token != NULL ){
...
some statements, some involving dynamically allocated memory
...
if ( check_this() ){
retval = 1;
goto out_free;
}
...
some more stuff
...
if(something()){
if ( check_that() ){
retval = 1;
goto out_free;
} else {
retval = 0;
goto out_free;
}
}
token = strtok(NULL,delim);
}
out_free:
free(var1);
free(var2);
...
free(varN);
return retval;
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
int function(const char * param)
{
int retval = 1;
char * var1 = fcn_that_returns_dynamically_allocated_string(param);
if( var1 == NULL ){
retval = 0;
goto out;
}
if( isValid(var1) ){
retval = some_function(var1);
goto out_free;
}
if( isGood(var1) ){
retval = 0;
goto out_free;
}
out_free:
free(var1);
out:
return retval;
}
Wenn wir das Äquivalent ohne goto
s betrachten:
int function(const char * param)
{
int retval = 1;
char * var1 = fcn_that_returns_dynamically_allocated_string(param);
if( var1 != NULL ){
if( isValid(var1) ){
retval = some_function(var1);
} else {
if( isGood(var1) ){
retval = 0;
}
}
free(var1);
} else {
retval = 0;
}
return retval;
}
Für mich ist es im ersten Fall offensichtlich, dass NULL
wir hier raus und zurückkehren , wenn die erste Funktion zurückkehrt 0
. 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, dass free()
Aussagen in der Mitte von Funktionen schlecht riechen und mir signalisieren, dass ich ein Unterprogramm erstellen sollte. In diesem Fall habe ich var1
in meiner Funktion erstellt und konnte es nicht free()
in einem Unterprogramm, aber das ist der Grund goto 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
, ein out_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
char *var1, *var2;
char line[MAX_LINE_LENGTH];
while( sscanf(line,... ){
var1 = functionA(line,count);
var2 = functionB(line,count);
if( functionC(var1, var2){
count++
continue;
}
...
a bunch of statements
...
count++;
free(var1);
free(var2);
}
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 frei var1
und brauchen var2
. Ich musste ein hinzufügen var3
, 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:
char *var1, *var2;
char line[MAX_LINE_LENGTH];
while( sscanf(line,... ){
var1 = functionA(line,count);
var2 = functionB(line,count);
var3 = newFunction(line,count);
if( functionC(var1, var2){
goto next;
}
...
a bunch of statements
...
next:
count++;
free(var1);
free(var2);
}
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 das next:
Etikett für mich unbefriedigend sind. Sie sind lediglich besser als das Spiegeln free()
der count++
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
#include <stddef.h>
#include <string.h>
char *
strtok(s, delim)
register char *s;
register const char *delim;
{
register char *spanp;
register int c, sc;
char *tok;
static char *last;
if (s == NULL && (s = last) == NULL)
return (NULL);
/*
* Skip (span) leading delimiters (s += strspn(s, delim), sort of).
*/
cont:
c = *s++;
for (spanp = (char *)delim; (sc = *spanp++) != 0;) {
if (c == sc)
goto cont;
}
if (c == 0) { /* no non-delimiter characters */
last = NULL;
return (NULL);
}
tok = s - 1;
/*
* Scan token (scan for delimiters: s += strcspn(s, delim), sort of).
* Note that delim must have one NUL; we stop if we see that, too.
*/
for (;;) {
c = *s++;
spanp = (char *)delim;
do {
if ((sc = *spanp++) == c) {
if (c == 0)
s = NULL;
else
s[-1] = 0;
last = s;
return (tok);
}
} while (sc != 0);
}
/* NOTREACHED */
}
Bitte korrigieren Sie mich, wenn ich falsch liege, aber ich glaube, dass das cont:
Label und die goto cont;
Aussage der Leistung dienen (sie machen den Code mit Sicherheit nicht besser lesbar). Sie könnten durch lesbaren Code ersetzt werden
while( isDelim(*s++,delim));
Begrenzer 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.
- Wenn Sie in einer while-Schleife eine continue-Anweisung haben, sollte der Rumpf der while-Schleife eine Funktion und die continue-Anweisung eine return-Anweisung sein.
- Wenn Sie in einer while-Schleife eine break-Anweisung haben, sollte die while-Schleife selbst eine Funktion und die break-Anweisung eine return-Anweisung sein.
- Wenn Sie beide haben, ist möglicherweise etwas nicht in Ordnung.
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.