Ist es jemals vorteilhaft, „goto“ in einer Sprache zu verwenden, die Schleifen und Funktionen unterstützt?Wenn ja warum?

StackOverflow https://stackoverflow.com/questions/24451

Frage

Den Eindruck habe ich schon lange gehabt goto sollten möglichst nie verwendet werden.Als ich neulich libavcodec (das in C geschrieben ist) durchstöberte, fielen mir mehrere Verwendungsmöglichkeiten auf.Ist es jemals vorteilhaft, es zu verwenden? goto in einer Sprache, die Schleifen und Funktionen unterstützt?Wenn ja warum?

War es hilfreich?

Lösung

Es gibt einige mir bekannte Gründe für die Verwendung der „goto“-Anweisung (einige haben bereits darüber gesprochen):

Sauberes Beenden einer Funktion

In einer Funktion müssen Sie häufig Ressourcen zuweisen und an mehreren Stellen beenden.Programmierer können ihren Code vereinfachen, indem sie den Ressourcenbereinigungscode am Ende der Funktion einfügen, und alle „Exit-Punkte“ der Funktion würden zum Bereinigungsetikett gelangen.Auf diese Weise müssen Sie nicht an jedem „Exit-Punkt“ der Funktion Bereinigungscode schreiben.

Verschachtelte Schleifen verlassen

Wenn Sie sich in einer verschachtelten Schleife befinden und aus ihr ausbrechen müssen alle Schleifen kann ein goto dies viel sauberer und einfacher machen als break-Anweisungen und if-Checks.

Leistungsverbesserungen auf niedriger Ebene

Dies ist nur in leistungskritischem Code gültig, aber goto-Anweisungen werden sehr schnell ausgeführt und können Ihnen beim Durchlaufen einer Funktion einen Schub geben.Dies ist jedoch ein zweischneidiges Schwert, da ein Compiler normalerweise keinen Code optimieren kann, der Gotos enthält.

Beachten Sie, dass in all diesen Beispielen gotos auf den Umfang einer einzelnen Funktion beschränkt sind.

Andere Tipps

Jeder, der dagegen istgoto zitiert direkt oder indirekt Edsger Dijkstra GoTo gilt als schädlich Artikel, um ihre Position zu untermauern.Schade, dass Dijkstras Artikel praktisch Nichts mit dem Weg zu tun goto Heutzutage werden Anweisungen verwendet, und daher ist das, was in dem Artikel gesagt wird, kaum oder gar nicht auf die moderne Programmierszene anwendbar.Der goto-less meme grenzt nun an eine Religion, bis hin zu seinen von oben diktierten Schriften, seinen Hohepriestern und der Meidung (oder Schlimmerem) vermeintlicher Ketzer.

Lassen Sie uns Dijkstras Artikel in einen Kontext stellen, um ein wenig Licht auf das Thema zu werfen.

Als Dijkstra seine Arbeit verfasste, waren die populären Sprachen der Zeit unstrukturierte prozedurale Sprachen wie BASIC, FORTRAN (die früheren Dialekte) und verschiedene Assemblersprachen.Es war durchaus üblich, dass Menschen, die höhere Sprachen verwendeten, einen Sprung machten überall in ihrer Codebasis in verdrehten, verdrehten Ausführungsfäden, die den Begriff „Spaghetti-Code“ hervorbrachten.Sie können dies sehen, indem Sie weitermachen das klassische Trek-Spiel geschrieben von Mike Mayfield und versucht herauszufinden, wie die Dinge funktionieren.Nehmen Sie sich einen Moment Zeit, um sich das anzusehen.

DAS ist „der ungezügelte Einsatz des Go-to-Statements“, gegen das Dijkstra 1968 in seinem Aufsatz schimpfte. DAS ist die Umgebung, in der er lebte, die ihn dazu veranlasste, diese Arbeit zu schreiben.Die Möglichkeit, an jeder beliebigen Stelle im Code an eine beliebige Stelle zu springen, kritisierte er und forderte ein Ende.Vergleicht man das mit den anämischen Kräften von goto in C oder anderen modernen Sprachen ist einfach lächerlich.

Ich kann bereits die lauten Gesänge der Kultisten hören, die sich dem Ketzer stellen.„Aber“, werden sie rufen, „man kann damit Code sehr schwer lesbar machen.“ goto in C.“ Ach ja?Sie können dazu führen, dass Code sehr schwer lesbar ist goto sowie.Wie dieser:

#define _ -F<00||--F-OO--;
int F=00,OO=00;main(){F_OO();printf("%1.3f\n",4.*-F/OO/OO);}F_OO()
{
            _-_-_-_
       _-_-_-_-_-_-_-_-_
    _-_-_-_-_-_-_-_-_-_-_-_
  _-_-_-_-_-_-_-_-_-_-_-_-_-_
 _-_-_-_-_-_-_-_-_-_-_-_-_-_-_
 _-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
 _-_-_-_-_-_-_-_-_-_-_-_-_-_-_
 _-_-_-_-_-_-_-_-_-_-_-_-_-_-_
  _-_-_-_-_-_-_-_-_-_-_-_-_-_
    _-_-_-_-_-_-_-_-_-_-_-_
        _-_-_-_-_-_-_-_
            _-_-_-_
}

Kein goto in Sichtweite, also muss es leicht zu lesen sein, oder?Oder wie wäre es mit diesem hier:

a[900];     b;c;d=1     ;e=1;f;     g;h;O;      main(k,
l)char*     *l;{g=      atoi(*      ++l);       for(k=
0;k*k<      g;b=k       ++>>1)      ;for(h=     0;h*h<=
g;++h);     --h;c=(     (h+=g>h     *(h+1))     -1)>>1;
while(d     <=g){       ++O;for     (f=0;f<     O&&d<=g
;++f)a[     b<<5|c]     =d++,b+=    e;for(      f=0;f<O
&&d<=g;     ++f)a[b     <<5|c]=     d++,c+=     e;e= -e
;}for(c     =0;c<h;     ++c){       for(b=0     ;b<k;++
b){if(b     <k/2)a[     b<<5|c]     ^=a[(k      -(b+1))
<<5|c]^=    a[b<<5      |c]^=a[     (k-(b+1     ))<<5|c]
;printf(    a[b<<5|c    ]?"%-4d"    :"    "     ,a[b<<5
|c]);}      putchar(    '\n');}}    /*Mike      Laman*/

NEIN goto dort auch.Es muss daher lesbar sein.

Was soll ich mit diesen Beispielen sagen?Es sind nicht Sprachfunktionen, die Code unlesbar und nicht wartbar machen.Es ist nicht die Syntax, die das bewirkt.Es sind schlechte Programmierer, die das verursachen.Und schlechte Programmierer können, wie Sie im obigen Punkt sehen können, Fehler machen beliebig Sprachfunktion unlesbar und unbrauchbar.Wie for Schleifen dort oben.(Sie können sie sehen, oder?)

Um fair zu sein: Einige Sprachkonstrukte sind leichter zu missbrauchen als andere.Wenn Sie jedoch ein C-Programmierer sind, würde ich mir etwa 50 % der Verwendungsmöglichkeiten von viel genauer ansehen #define lange bevor ich einen Kreuzzug dagegen unternehmen würde goto!

Für diejenigen, die sich die Mühe gemacht haben, bis hierher zu lesen, gibt es einige wichtige Punkte zu beachten.

  1. Dijkstras Artikel über goto Anweisungen wurden für eine Programmierumgebung geschrieben, in der goto war ein vielpotenziell schädlicher als in den meisten modernen Sprachen, die kein Assembler sind.
  2. Alle Verwendungen von werden automatisch weggeworfen goto Aus diesem Grund ist es ungefähr so ​​rational wie "Ich habe versucht, einmal Spaß zu haben, aber es hat es nicht gefallen, also bin ich jetzt dagegen."
  3. Es gibt legitime Verwendungen des modernen (anämischen) goto Anweisungen in Code, die nicht angemessen durch andere Konstrukte ersetzt werden können.
  4. Natürlich gibt es auch illegitime Verwendungen derselben Aussagen.
  5. Es gibt auch illegitime Verwendungen der modernen Kontrollanweisungen wie „godo„Abscheulichkeit, wo eine immer-falsche do Die Schleife wird aus der Verwendung ausgebrochen break anstelle von a goto.Diese sind oft schlimmer als der umsichtige Einsatz von goto.

Best Practices blind zu befolgen ist keine Best Practice.Die Idee des Vermeidens goto Anweisungen, da die primäre Form der Flusskontrolle darin besteht, die Erzeugung von unlesbarem Spaghetti-Code zu vermeiden.Bei sparsamer Verwendung an den richtigen Stellen können sie manchmal die einfachste und klarste Art sein, eine Idee auszudrücken.Walter Bright, der Erfinder des Zortech C++-Compilers und der Programmiersprache D, verwendet sie häufig, aber mit Bedacht.Auch mit dem goto Anweisungen ist sein Code immer noch perfekt lesbar.

Endeffekt:Vermeiden goto um es zu vermeiden goto ist sinnlos.Was Sie wirklich vermeiden möchten, ist die Produktion von unlesbarem Code.Wenn dein gotoWenn der beladene Code lesbar ist, ist daran nichts auszusetzen.

Seit goto macht es schwierig, über den Programmablauf nachzudenken1 (auch bekannt als„Spaghetti-Code“), goto wird im Allgemeinen nur zum Ausgleich fehlender Funktionen verwendet:Die Verwendung von goto kann tatsächlich akzeptabel sein, aber nur, wenn die Sprache keine strukturiertere Variante bietet, um das gleiche Ziel zu erreichen.Nehmen Sie das Beispiel von Doubt:

Die Regel mit goto, die wir verwenden, ist, dass goto in Ordnung ist, um zu einem einzelnen Exit-Bereinigungspunkt in einer Funktion vorwärts zu springen.

Das stimmt – aber nur, wenn die Sprache keine strukturierte Ausnahmebehandlung mit Bereinigungscode zulässt (wie RAII oder finally), das die gleiche Aufgabe besser erledigt (da es speziell dafür entwickelt wurde), oder wenn es einen guten Grund gibt, keine strukturierte Ausnahmebehandlung zu verwenden (aber Sie werden diesen Fall nie haben, außer auf einer sehr niedrigen Ebene).

In den meisten anderen Sprachen ist die einzig akzeptable Verwendung von goto besteht darin, verschachtelte Schleifen zu verlassen.Und selbst da ist es fast immer besser, die äußere Schleife in eine eigene Methode zu heben und zu nutzen return stattdessen.

Abgesehen davon, goto ist ein Zeichen dafür, dass nicht ausreichend über den jeweiligen Codeabschnitt nachgedacht wurde.


1 Moderne Sprachen, die unterstützen goto einige Einschränkungen implementieren (z.B. goto darf nicht in oder aus Funktionen springen), aber das Problem bleibt im Grunde dasselbe.

Das Gleiche gilt übrigens natürlich auch für andere Sprachmerkmale, insbesondere Ausnahmen.Und es gibt in der Regel strenge Regeln, um diese Funktionen nur dort zu verwenden, wo sie angegeben sind, beispielsweise die Regel, keine Ausnahmen zur Steuerung des nicht außergewöhnlichen Programmflusses zu verwenden.

Nun, es gibt eine Sache, die immer schlimmer ist als goto's;seltsame Verwendung anderer Programmablaufoperatoren, um ein Goto zu vermeiden:

Beispiele:

    // 1
    try{
      ...
      throw NoErrorException;
      ...
    } catch (const NoErrorException& noe){
      // This is the worst
    } 


    // 2
    do {
      ...break; 
      ...break;
    } while (false);


    // 3
    for(int i = 0;...) { 
      bool restartOuter = false;
      for (int j = 0;...) {
        if (...)
          restartOuter = true;
      if (restartOuter) {
        i = -1;
      }
    }

etc
etc

In C# schalten Stellungnahme lässt kein Durchfallen zu.Also gehe zu wird verwendet, um die Steuerung auf ein bestimmtes Schaltgehäuseetikett oder das zu übertragen Standard Etikett.

Zum Beispiel:

switch(value)
{
  case 0:
    Console.Writeln("In case 0");
    goto case 1;
  case 1:
    Console.Writeln("In case 1");
    goto case 2;
  case 2:
    Console.Writeln("In case 2");
    goto default;
  default:
    Console.Writeln("In default");
    break;
}

Bearbeiten:Es gibt eine Ausnahme bei der „Kein Durchfall“-Regel.Ein Fall-Through ist zulässig, wenn eine case-Anweisung keinen Code enthält.

#ifdef TONGUE_IN_CHEEK

Perl hat eine goto Damit können Sie auch „Poor-Man's-Tail-Calls“ umsetzen.:-P

sub factorial {
    my ($n, $acc) = (@_, 1);
    return $acc if $n < 1;
    @_ = ($n - 1, $acc * $n);
    goto &factorial;
}

#endif

Okay, das hat also nichts mit C's zu tun goto.Im Ernst: Ich stimme den anderen Kommentaren zur Verwendung zu goto für Aufräumarbeiten oder für die Umsetzung Duffs Gerät, oder dergleichen.Es geht um Gebrauch, nicht um Missbrauch.

(Derselbe Kommentar kann gelten für longjmp, Ausnahmen, call/cc, und dergleichen – sie haben legitime Verwendungszwecke, können aber leicht missbraucht werden.Zum Beispiel das Auslösen einer Ausnahme, nur um einer tief verschachtelten Kontrollstruktur zu entgehen, und zwar unter absolut nicht außergewöhnlichen Umständen.)

Ich habe im Laufe der Jahre mehr als ein paar Zeilen Assembler geschrieben.Letztendlich wird jede Hochsprache bis hin zu Gotos kompiliert.Okay, nennen Sie sie „Zweige“ oder „Sprünge“ oder wie auch immer, aber es sind Gotos.Kann jemand Goto-less-Assembler schreiben?

Natürlich können Sie einen Fortran-, C- oder BASIC-Programmierer darauf hinweisen, dass es ein Rezept für Spaghetti Bolognaise ist, sich mit Gotos auszutoben.Die Antwort liegt jedoch nicht darin, sie zu meiden, sondern sie mit Bedacht zu nutzen.

Mit einem Messer kann man Essen zubereiten, jemanden befreien oder töten.Verzichten wir aus Angst davor auf Messer?Ebenso das goto:Bei unvorsichtigem Gebrauch behindert es, bei vorsichtigem Gebrauch hilft es.

Schauen Sie mal rein Wann sollte Goto beim Programmieren in C verwendet werden?:

Obwohl die Verwendung von goto fast immer eine schlechte Programmierpraxis ist (sicherlich gibt es eine bessere Möglichkeit, XYZ auszuführen), gibt es Zeiten, in denen es wirklich keine schlechte Wahl ist.Manche könnten sogar argumentieren, dass es die beste Wahl ist, wenn es nützlich ist.

Das meiste, was ich über goto zu sagen habe, gilt eigentlich nur für C.Wenn Sie C++ verwenden, gibt es keinen vernünftigen Grund, goto anstelle von Ausnahmen zu verwenden.In C verfügen Sie jedoch nicht über die Leistungsfähigkeit eines Ausnahmebehandlungsmechanismus. Wenn Sie also die Fehlerbehandlung vom Rest Ihrer Programmlogik trennen und vermeiden möchten, dass der Bereinigungscode im gesamten Code mehrmals neu geschrieben wird, Dann kann goto eine gute Wahl sein.

Was meine ich?Möglicherweise haben Sie einen Code, der so aussieht:

int big_function()
{
    /* do some work */
    if([error])
    {
        /* clean up*/
        return [error];
    }
    /* do some more work */
    if([error])
    {
        /* clean up*/
        return [error];
    }
    /* do some more work */
    if([error])
    {
        /* clean up*/
        return [error];
    }
    /* do some more work */
    if([error])
    {
        /* clean up*/
        return [error];
    }
    /* clean up*/
    return [success];
}

Das ist in Ordnung, bis Sie feststellen, dass Sie Ihren Bereinigungscode ändern müssen.Dann müssen Sie durchgehen und 4 Änderungen vornehmen.Nun könnten Sie entscheiden, dass Sie einfach die gesamte Bereinigung in einer einzigen Funktion kapseln können;Das ist keine schlechte Idee.Das bedeutet jedoch, dass Sie mit Zeigern vorsichtig sein müssen – wenn Sie vorhaben, einen Zeiger in Ihrer Bereinigungsfunktion freizugeben, gibt es keine Möglichkeit, ihn so zu setzen, dass er dann auf NULL zeigt, es sei denn, Sie übergeben einen Zeiger an einen Zeiger.In vielen Fällen werden Sie diesen Zeiger sowieso nicht mehr verwenden, sodass dies möglicherweise kein großes Problem darstellt.Wenn Sie andererseits einen neuen Zeiger, ein neues Dateihandle oder eine andere Sache hinzufügen, die bereinigt werden muss, müssen Sie Ihre Bereinigungsfunktion erneut ändern.und dann müssen Sie die Argumente dieser Funktion ändern.

Durch die Nutzung goto, es wird sein

int big_function()
{
    int ret_val = [success];
    /* do some work */
    if([error])
    {
        ret_val = [error];
        goto end;
    }
    /* do some more work */
    if([error])
    {
        ret_val = [error];
        goto end;
    }
    /* do some more work */
    if([error])
    {
        ret_val = [error];
        goto end;
    }
    /* do some more work */
    if([error])
    {
        ret_val = [error];
        goto end;
    }
end:
    /* clean up*/
    return ret_val;
}

Der Vorteil hierbei ist, dass Ihr Code nach dem Ende Zugriff auf alles hat, was er für die Bereinigung benötigt, und Sie es geschafft haben, die Anzahl der Änderungspunkte erheblich zu reduzieren.Ein weiterer Vorteil besteht darin, dass Sie nicht mehr mehrere Ausgangspunkte für Ihre Funktion haben, sondern nur noch einen;Es besteht keine Gefahr, dass Sie versehentlich von der Veranstaltung zurückkehren, ohne aufzuräumen.

Da goto außerdem nur zum Springen zu einem einzelnen Punkt verwendet wird, ist es nicht so, dass Sie eine Menge Spaghetti-Code erstellen, der hin und her springt, um Funktionsaufrufe zu simulieren.Goto hilft vielmehr dabei, strukturierteren Code zu schreiben.


In einem Wort, goto sollte immer sparsam und als letztes Mittel eingesetzt werden – aber es gibt eine Zeit und einen Ort dafür.Die Frage sollte nicht lauten: „Muss man es verwenden“, sondern „Ist es die beste Wahl“, es zu verwenden?

Einer der Gründe, warum goto schlecht ist, ist neben dem Codierungsstil, dass man damit etwas erstellen kann überlappend, Aber nicht verschachtelt Schleifen:

loop1:
  a
loop2:
  b
  if(cond1) goto loop1
  c
  if(cond2) goto loop2

Dies würde die bizarre, aber möglicherweise legale Flussstruktur schaffen, in der eine Sequenz wie (a, b, c, b, a, b, a, b, ...) möglich ist, was Compiler-Hacker unzufrieden macht.Offenbar gibt es eine Reihe cleverer Optimierungstricks, die darauf basieren, dass solche Strukturen nicht auftreten.(Ich sollte meine Kopie des Drachenbuchs überprüfen ...) Dies könnte (bei Verwendung einiger Compiler) dazu führen, dass keine anderen Optimierungen für Code durchgeführt werden, der enthält gotoS.

Es könnte nützlich sein, wenn Sie wissen es überredet den Compiler übrigens nur, schnelleren Code auszugeben.Persönlich würde ich lieber versuchen, dem Compiler zu erklären, was wahrscheinlich ist und was nicht, bevor ich einen Trick wie goto verwende, aber ich könnte es wahrscheinlich auch versuchen goto bevor ich den Assembler hacke.

Ich finde es lustig, dass einige Leute so weit gehen, eine Liste von Fällen zu erstellen, in denen goto akzeptabel ist, und sagen, dass alle anderen Verwendungen inakzeptabel seien.Glauben Sie wirklich, dass Sie jeden Fall kennen, in dem goto die beste Wahl ist, um einen Algorithmus auszudrücken?

Zur Veranschaulichung gebe ich Ihnen ein Beispiel, das hier noch niemand gezeigt hat:

Heute habe ich Code zum Einfügen eines Elements in eine Hash-Tabelle geschrieben.Die Hash-Tabelle ist ein Cache früherer Berechnungen, der nach Belieben überschrieben werden kann (was Auswirkungen auf die Leistung, aber nicht auf die Richtigkeit hat).

Jeder Bucket der Hash-Tabelle hat 4 Slots, und ich habe eine Reihe von Kriterien, um zu entscheiden, welches Element überschrieben werden soll, wenn ein Bucket voll ist.Im Moment bedeutet das, bis zu drei Durchgänge durch einen Eimer durchzuführen, etwa so:

// Overwrite an element with same hash key if it exists
for (add_index=0; add_index < ELEMENTS_PER_BUCKET; add_index++)
  if (slot_p[add_index].hash_key == hash_key)
    goto add;

// Otherwise, find first empty element
for (add_index=0; add_index < ELEMENTS_PER_BUCKET; add_index++)
  if ((slot_p[add_index].type == TT_ELEMENT_EMPTY)
    goto add;

// Additional passes go here...

add:
// element is written to the hash table here

Wie würde dieser Code aussehen, wenn ich goto nicht verwenden würde?

Etwas wie das:

// Overwrite an element with same hash key if it exists
for (add_index=0; add_index < ELEMENTS_PER_BUCKET; add_index++)
  if (slot_p[add_index].hash_key == hash_key)
    break;

if (add_index >= ELEMENTS_PER_BUCKET) {
  // Otherwise, find first empty element
  for (add_index=0; add_index < ELEMENTS_PER_BUCKET; add_index++)
    if ((slot_p[add_index].type == TT_ELEMENT_EMPTY)
      break;
  if (add_index >= ELEMENTS_PER_BUCKET)
   // Additional passes go here (nested further)...
}

// element is written to the hash table here

Es würde immer schlimmer aussehen, wenn weitere Durchgänge hinzugefügt würden, während die Version mit goto jederzeit die gleiche Einrückungsebene beibehält und die Verwendung falscher if-Anweisungen vermeidet, deren Ergebnis durch die Ausführung der vorherigen Schleife impliziert wird.

Es gibt also einen weiteren Fall, in dem goto den Code sauberer und einfacher zu schreiben und zu verstehen macht ...Ich bin mir sicher, dass es noch viele weitere gibt. Geben Sie also nicht vor, alle Fälle zu kennen, in denen goto nützlich ist, und verwerfen Sie alle guten Fälle, die Ihnen nicht einfallen.

Die Regel mit goto, die wir verwenden, ist, dass goto in Ordnung ist, um zu einem einzelnen Exit-Bereinigungspunkt in einer Funktion vorwärts zu springen.Bei wirklich komplexen Funktionen lockern wir diese Regel, um anderen einen Sprung nach vorne zu ermöglichen.In beiden Fällen vermeiden wir tief verschachtelte if-Anweisungen, die bei der Fehlercodeprüfung häufig vorkommen, was die Lesbarkeit und Wartung verbessert.

Die nachdenklichste und gründlichste Diskussion von Goto-Anweisungen, ihrer legitimen Verwendung und alternativen Konstrukten, die anstelle von „tugendhaften Goto-Anweisungen“ verwendet werden können, aber genauso leicht missbraucht werden können wie Goto-Anweisungen, ist Donald Knuths Artikel „Strukturierte Programmierung mit goto-Anweisungen", in den Computing Surveys vom Dezember 1974 (Band 6, Nr.4.S.261 - 301).

Es überrascht nicht, dass einige Aspekte dieser 39 Jahre alten Arbeit veraltet sind:Die Steigerung der Rechenleistung um Größenordnungen macht einige von Knuths Leistungsverbesserungen bei mittelgroßen Problemen unbemerkt, und seitdem wurden neue Programmiersprachenkonstrukte erfunden.(Try-Catch-Blöcke fassen zum Beispiel Zahns Konstrukt zusammen, obwohl sie selten auf diese Weise verwendet werden.) Aber Knuth deckt alle Seiten des Arguments ab und sollte Pflichtlektüre sein, bevor jemand das Thema noch einmal aufwärmt.

In einem Perl-Modul möchten Sie gelegentlich Unterroutinen oder Abschlüsse im laufenden Betrieb erstellen.Die Sache ist die Frage: Wie kommt man zu der Unterroutine, wenn man sie erst einmal erstellt hat?Sie könnten es einfach aufrufen, aber dann, wenn die Unterroutine verwendet wird caller() Es wird nicht so hilfreich sein, wie es sein könnte.Das ist, wo die goto &subroutine Abwechslung kann hilfreich sein.

Hier ist ein kurzes Beispiel:

sub AUTOLOAD{
  my($self) = @_;
  my $name = $AUTOLOAD;
  $name =~ s/.*:://;

  *{$name} = my($sub) = sub{
    # the body of the closure
  }

  goto $sub;

  # nothing after the goto will ever be executed.
}

Sie können auch dieses Formular verwenden goto um eine rudimentäre Form der Tail-Call-Optimierung bereitzustellen.

sub factorial($){
  my($n,$tally) = (@_,1);

  return $tally if $n <= 1;

  $tally *= $n--;
  @_ = ($n,$tally);
  goto &factorial;
}

( In Perl 5 Version 16 das wäre besser geschrieben als goto __SUB__; )

Es gibt ein Modul, das a importiert tail Modifikator und einen, der importiert wird recur wenn Sie diese Form nicht verwenden möchten goto.

use Sub::Call::Tail;
sub AUTOLOAD {
  ...
  tail &$sub( @_ );
}

use Sub::Call::Recur;
sub factorial($){
  my($n,$tally) = (@_,1);

  return $tally if $n <= 1;
  recur( $n-1, $tally * $n );
}

Die meisten anderen Gründe für die Verwendung goto gelingt besser mit anderen Schlüsselwörtern.

Wie redoing ein bisschen Code:

LABEL: ;
...
goto LABEL if $x;
{
  ...
  redo if $x;
}

Oder zum gehen last eines Codes von mehreren Stellen:

goto LABEL if $x;
...
goto LABEL if $y;
...
LABEL: ;
{
  last if $x;
  ...
  last if $y
  ...
}

Wenn ja warum?

C hat keine mehrstufige/beschriftete Unterbrechung und nicht alle Kontrollflüsse können einfach mit den Iterations- und Entscheidungsprimitiven von C modelliert werden.Gotos leisten einen großen Beitrag zur Behebung dieser Mängel.

Manchmal ist es klarer, eine Flag-Variable zu verwenden, um eine Art Pseudo-Mehrebenen-Umbruch zu bewirken, aber sie ist dem goto nicht immer überlegen (zumindest ermöglicht ein goto im Gegensatz zu einer Flag-Variablen, leicht zu bestimmen, wohin die Steuerung geht). ), und manchmal möchte man einfach nicht den Leistungspreis von Flags/anderen Verzerrungen zahlen, um das Goto zu vermeiden.

libavcodec ist ein leistungsempfindlicher Code.Der direkte Ausdruck des Kontrollflusses hat wahrscheinlich Priorität, da er tendenziell besser funktioniert.

Genauso gut hat noch nie jemand die „COME FROM“-Anweisung umgesetzt …

Ich finde die Verwendung von do{} while(false) absolut abstoßend.Es ist denkbar, dass es mich in manchen Fällen davon überzeugt, dass es notwendig ist, aber niemals, dass es sich um sauberen, vernünftigen Code handelt.

Wenn Sie eine solche Schleife ausführen müssen, warum machen Sie dann nicht die Abhängigkeit von der Flag-Variablen explizit?

for (stepfailed=0 ; ! stepfailed ; /*empty*/)

Das GOTO kann natürlich verwendet werden, aber es gibt noch eine wichtigere Sache als den Codestil oder die Frage, ob der Code lesbar ist oder nicht, die Sie bei der Verwendung berücksichtigen müssen: Der darin enthaltene Code ist möglicherweise nicht so robust, wie Sie denken.

Schauen Sie sich zum Beispiel die folgenden zwei Codeausschnitte an:

If A <> 0 Then A = 0 EndIf
Write("Value of A:" + A)

Ein äquivalenter Code mit GOTO

If A == 0 Then GOTO FINAL EndIf
   A = 0
FINAL:
Write("Value of A:" + A)

Das erste, was wir denken, ist, dass das Ergebnis beider Codebits der Wert von A sein wird:0" (wir gehen natürlich von einer Ausführung ohne Parallelität aus)

Das ist nicht korrekt:Im ersten Beispiel ist A immer 0, aber im zweiten Beispiel (mit der GOTO-Anweisung) ist A möglicherweise nicht 0.Warum?

Der Grund liegt darin, dass ich an einer anderen Stelle des Programms eine einfügen kann GOTO FINAL ohne den Wert von A zu kontrollieren.

Dieses Beispiel ist sehr offensichtlich, aber je komplizierter die Programme werden, desto schwieriger wird es, solche Dinge zu erkennen.

Verwandtes Material finden Sie im berühmten Artikel von Mr.Dijkstra „Ein Fall gegen die GO TO-Anweisung“

1) Die häufigste Verwendung von goto, die ich kenne, ist die Emulation der Ausnahmebehandlung in Sprachen, die dies nicht bieten, nämlich in C.(Der von Nuclear oben bereitgestellte Code ist genau das.) Schauen Sie sich den Linux-Quellcode an und Sie werden eine Unmenge von Gotos sehen, die auf diese Weise verwendet werden.Laut einer Schnellumfrage aus dem Jahr 2013 gab es etwa 100.000 Gotos im Linux-Code: http://blog.regehr.org/archives/894.Die Goto-Nutzung wird sogar im Linux-Coding-Styleguide erwähnt: https://www.kernel.org/doc/Documentation/CodingStyle.So wie objektorientierte Programmierung mithilfe von mit Funktionszeigern gefüllten Strukturen emuliert wird, hat goto seinen Platz in der C-Programmierung.Wer hat also recht:Dijkstra oder Linus (und alle Linux-Kernel-Programmierer)?Es ist Theorie vs.grundsätzlich üben.

Es gibt jedoch das übliche Problem, wenn keine Unterstützung auf Compiler-Ebene und keine Prüfungen für gängige Konstrukte/Muster vorhanden sind:Ohne Überprüfungen zur Kompilierungszeit ist es einfacher, sie falsch zu verwenden und Fehler einzuführen.Windows und Visual C++ bieten im C-Modus aus genau diesem Grund eine Ausnahmebehandlung über SEH/VEH an:Ausnahmen sind auch außerhalb von OOP-Sprachen nützlich, d. h.in einer prozeduralen Sprache.Aber der Compiler kann Ihnen nicht immer helfen, selbst wenn er syntaktische Unterstützung für Ausnahmen in der Sprache bietet.Betrachten Sie als Beispiel für den letzteren Fall den berühmten Apple SSL „goto fail“-Bug, der nur einen goto dupliziert hat, mit katastrophalen Folgen (https://www.imperialviolet.org/2014/02/22/applebug.html):

if (something())
  goto fail;
  goto fail; // copypasta bug
printf("Never reached\n");
fail:
  // control jumps here

Sie können genau den gleichen Fehler haben, wenn Sie vom Compiler unterstützte Ausnahmen verwenden, z.in C++:

struct Fail {};

try {
  if (something())
    throw Fail();
    throw Fail(); // copypasta bug
  printf("Never reached\n");
}
catch (Fail&) {
  // control jumps here
}

Beide Varianten des Fehlers können jedoch vermieden werden, wenn der Compiler den Code analysiert und Sie vor nicht erreichbarem Code warnt.Wenn Sie beispielsweise mit Visual C++ auf der Warnstufe /W4 kompilieren, wird der Fehler in beiden Fällen gefunden.Java zum Beispiel verbietet nicht erreichbaren Code (wo er ihn finden kann!) aus einem ziemlich guten Grund:Es handelt sich wahrscheinlich um einen Fehler im durchschnittlichen Joe-Code.Solange das goto-Konstrukt keine Ziele zulässt, die der Compiler nicht leicht herausfinden kann, wie z. B. Gotos zu berechneten Adressen(**), ist es für den Compiler nicht schwieriger, mit Gotos nicht erreichbaren Code in einer Funktion zu finden, als mit Dijkstra -genehmigter Code.

(**) Fußnote:Springen zu berechneten Zeilennummern ist in einigen Basic-Versionen möglich, z.B.GOTO 10*x wobei x eine Variable ist.Ziemlich verwirrend ist, dass sich „berechnetes goto“ in Fortran auf ein Konstrukt bezieht, das einer switch-Anweisung in C entspricht.Standard-C erlaubt keine berechneten Gotos in der Sprache, sondern nur Gotos zu statisch/syntaktisch deklarierten Labels.GNU C verfügt jedoch über eine Erweiterung, um die Adresse eines Labels abzurufen (den unären Präfix-&&-Operator) und ermöglicht auch einen Zugriff auf eine Variable vom Typ void*.Sehen https://gcc.gnu.org/onlinedocs/gcc/Labels-as-Values.html Weitere Informationen zu diesem obskuren Unterthema.Der Rest dieses Beitrags befasst sich nicht mit dieser obskuren GNU-C-Funktion.

Standard C (d. h.nicht berechnet) Gotos sind normalerweise nicht der Grund dafür, dass nicht erreichbarer Code zur Kompilierungszeit nicht gefunden werden kann.Der übliche Grund ist ein Logikcode wie der folgende.Gegeben

int computation1() {
  return 1;
}

int computation2() {
  return computation1();
}

Für einen Compiler ist es genauso schwierig, in einem der folgenden drei Konstrukte nicht erreichbaren Code zu finden:

void tough1() {
  if (computation1() != computation2())
    printf("Unreachable\n");
}

void tough2() {
  if (computation1() == computation2())
    goto out;
  printf("Unreachable\n");
out:;
}

struct Out{};

void tough3() {
  try {
    if (computation1() == computation2())
      throw Out();
    printf("Unreachable\n");
  }
  catch (Out&) {
  }
}

(Entschuldigen Sie meinen mit Klammern verbundenen Codierungsstil, aber ich habe versucht, die Beispiele so kompakt wie möglich zu halten.)

Visual C++ /W4 (selbst mit /Ox) kann in keinem dieser Codes unerreichbaren Code finden, und wie Sie wahrscheinlich wissen, ist das Problem, nicht erreichbaren Code zu finden, im Allgemeinen unentscheidbar.(Wenn Sie mir das nicht glauben: https://www.cl.cam.ac.uk/teaching/2006/OptComp/slides/lecture02.pdf)

Als verwandtes Problem kann C goto verwendet werden, um Ausnahmen nur innerhalb des Funktionskörpers zu emulieren.Die Standard-C-Bibliothek bietet ein Funktionspaar setjmp() und longjmp() zum Emulieren nicht-lokaler Exits/Ausnahmen, diese weisen jedoch im Vergleich zu dem, was andere Sprachen bieten, einige gravierende Nachteile auf.Der Wikipedia-Artikel http://en.wikipedia.org/wiki/Setjmp.h erklärt dieses letztere Problem ziemlich gut.Dieses Funktionspaar funktioniert auch unter Windows (http://msdn.microsoft.com/en-us/library/yz2ez4as.aspx), aber kaum jemand nutzt sie dort, weil SEH/VEH überlegen ist.Selbst unter Unix werden setjmp und longjmp meiner Meinung nach sehr selten verwendet.

2) Ich denke, die zweithäufigste Verwendung von goto in C ist die Implementierung von mehrstufigem Break oder mehrstufigem Fortsetzen, was ebenfalls ein ziemlich unumstrittener Anwendungsfall ist.Denken Sie daran, dass Java kein Goto-Label, aber ein Break-Label oder ein Continue-Label zulässt.Entsprechend http://www.oracle.com/technetwork/java/simple-142616.html, Dies ist tatsächlich der häufigste Anwendungsfall von Gotos in C (90 % sagen sie), aber meiner subjektiven Erfahrung nach verwendet Systemcode Gotos häufiger zur Fehlerbehandlung.In wissenschaftlichem Code oder dort, wo das Betriebssystem eine Ausnahmebehandlung bietet (Windows), sind mehrstufige Exits vielleicht der vorherrschende Anwendungsfall.Sie machen keine wirklichen Angaben zum Kontext ihrer Umfrage.

Bearbeitet, um Folgendes hinzuzufügen:Es stellt sich heraus, dass diese beiden Verwendungsmuster im C-Buch von Kernighan und Ritchie auf Seite 60 zu finden sind (je nach Ausgabe).Bemerkenswert ist auch, dass beide Anwendungsfälle nur Vorwärts-Gotos beinhalten.Und es stellt sich heraus, dass die MISRA C-Ausgabe 2012 (im Gegensatz zur Ausgabe 2004) jetzt Gotos zulässt, solange es sich nur um Vorwärts-Gotos handelt.

In Perl die Verwendung einer Bezeichnung zum „Gehe zu“ aus einer Schleife – unter Verwendung einer „last“-Anweisung, die break ähnelt.

Dies ermöglicht eine bessere Kontrolle über verschachtelte Schleifen.

Das traditionelle Goto Etikett wird ebenfalls unterstützt, aber ich bin mir nicht sicher, ob es zu viele Fälle gibt, in denen dies die einzige Möglichkeit ist, das zu erreichen, was Sie wollen – Unterroutinen und Schleifen sollten in den meisten Fällen ausreichen.

Das Problem mit „goto“ und das wichtigste Argument der „goto-less programming“-Bewegung besteht darin, dass Ihr Code, wenn Sie ihn zu häufig verwenden, zwar korrekt verhält, aber unlesbar, nicht wartbar, nicht überprüfbar usw. wird.In 99,99 % der Fälle führt „goto“ zu Spaghetti-Code.Persönlich kann ich mir keinen guten Grund vorstellen, warum ich „goto“ verwenden würde.

Edsger Dijkstra, ein Informatiker, der bedeutende Beiträge auf diesem Gebiet geleistet hat, war auch für seine Kritik an der Verwendung von GoTo bekannt.Es gibt einen kurzen Artikel über seine Argumentation auf Wikipedia.

Ich verwende goto im folgenden Fall:wenn die Rückkehr von Funktionen an verschiedenen Orten erforderlich ist und vor der Rückkehr eine gewisse Nichtinitialisierung durchgeführt werden muss:

Non-goto-Version:

int doSomething (struct my_complicated_stuff *ctx)    
{
    db_conn *conn;
    RSA *key;
    char *temp_data;
    conn = db_connect();  


    if (ctx->smth->needs_alloc) {
      temp_data=malloc(ctx->some_size);
      if (!temp_data) {
        db_disconnect(conn);
        return -1;      
        }
    }

    ...

    if (!ctx->smth->needs_to_be_processed) {
        free(temp_data);    
        db_disconnect(conn);    
        return -2;
    }

    pthread_mutex_lock(ctx->mutex);

    if (ctx->some_other_thing->error) {
        pthread_mutex_unlock(ctx->mutex);
        free(temp_data);
        db_disconnect(conn);        
        return -3;  
    }

    ...

    key=rsa_load_key(....);

    ...

    if (ctx->something_else->error) {
         rsa_free(key); 
         pthread_mutex_unlock(ctx->mutex);
         free(temp_data);
         db_disconnect(conn);       
         return -4;  
    }

    if (ctx->something_else->additional_check) {
         rsa_free(key); 
         pthread_mutex_unlock(ctx->mutex);
         free(temp_data);
         db_disconnect(conn);       
         return -5;  
    }


    pthread_mutex_unlock(ctx->mutex);
    free(temp_data);    
    db_disconnect(conn);    
    return 0;     
}

Gehe zur Version:

int doSomething_goto (struct my_complicated_stuff *ctx)
{
    int ret=0;
    db_conn *conn;
    RSA *key;
    char *temp_data;
    conn = db_connect();  


    if (ctx->smth->needs_alloc) {
      temp_data=malloc(ctx->some_size);
      if (!temp_data) {
            ret=-1;
           goto exit_db;   
          }
    }

    ...

    if (!ctx->smth->needs_to_be_processed) {
        ret=-2;
        goto exit_freetmp;      
    }

    pthread_mutex_lock(ctx->mutex);

    if (ctx->some_other_thing->error) {
        ret=-3;
        goto exit;  
    }

    ...

    key=rsa_load_key(....);

    ...

    if (ctx->something_else->error) {
        ret=-4;
        goto exit_freekey; 
    }

    if (ctx->something_else->additional_check) {
        ret=-5;
        goto exit_freekey;  
    }

exit_freekey:
    rsa_free(key);
exit:    
    pthread_mutex_unlock(ctx->mutex);
exit_freetmp:
    free(temp_data);        
exit_db:
    db_disconnect(conn);    
    return ret;     
}

Die zweite Version macht es einfacher, wenn Sie etwas in den Freigabeanweisungen ändern müssen (jede wird einmal im Code verwendet), und verringert die Wahrscheinlichkeit, eine davon zu überspringen, wenn Sie einen neuen Zweig hinzufügen.Das Verschieben in einer Funktion hilft hier nicht weiter, da die Freigabe auf verschiedenen „Ebenen“ erfolgen kann.

Einige sagen, dass es in C++ keinen Grund für goto gibt.Manche sagen, dass es in 99 % der Fälle bessere Alternativen gibt. Das ist keine Argumentation, sondern nur irrationale Eindrücke. Hier ist ein solides Beispiel, bei dem goto zu einem schönen Code führt, so etwas wie einer erweiterten Do-While-Schleife:

int i;

PROMPT_INSERT_NUMBER:
  std::cout << "insert number: ";
  std::cin >> i;
  if(std::cin.fail()) {
    std::cin.clear();
    std::cin.ignore(1000,'\n');
    goto PROMPT_INSERT_NUMBER;          
  }

std::cout << "your number is " << i;

Vergleichen Sie es mit Goto-Free-Code:

int i;

bool loop;
do {
  loop = false;
  std::cout << "insert number: ";
  std::cin >> i;
  if(std::cin.fail()) {
    std::cin.clear();
    std::cin.ignore(1000,'\n');
    loop = true;          
  }
} while(loop);

std::cout << "your number is " << i;

Ich sehe diese Unterschiede:

  • verschachtelt {} Block wird benötigt (allerdings do {...} while kommt mir bekannter vor)
  • extra loop Es wird eine Variable benötigt, die an vier Stellen verwendet wird
  • Es dauert länger, die Arbeit mit dem zu lesen und zu verstehen loop
  • Die loop enthält keine Daten, sondern steuert lediglich den Ausführungsfluss, was weniger verständlich ist als eine einfache Beschriftung

Es gibt noch ein weiteres Beispiel

void sort(int* array, int length) {
SORT:
  for(int i=0; i<length-1; ++i) if(array[i]>array[i+1]) {
    swap(data[i], data[i+1]);
    goto SORT; // it is very easy to understand this code, right?
  }
}

Jetzt lasst uns das „Böse“ loswerden, gehe zu:

void sort(int* array, int length) {
  bool seemslegit;
  do {
    seemslegit = true;
    for(int i=0; i<length-1; ++i) if(array[i]>array[i+1]) {
      swap(data[i], data[i+1]);
      seemslegit = false;
    }
  } while(!seemslegit);
}

Sie sehen, es handelt sich um die gleiche Art der Verwendung von „goto“, es handelt sich um ein gut strukturiertes Muster und es handelt sich nicht um die von vielen empfohlene „Forward Goto“-Methode, sondern um die einzig empfohlene Methode.Sicherlich möchten Sie „intelligenten“ Code wie diesen vermeiden:

void sort(int* array, int length) {
  for(int i=0; i<length-1; ++i) if(array[i]>array[i+1]) {
    swap(data[i], data[i+1]);
    i = -1; // it works, but WTF on the first glance
  }
}

Der Punkt ist, dass goto leicht missbraucht werden kann, aber goto selbst ist nicht schuld daran.Beachten Sie, dass label in C++ über einen Funktionsbereich verfügt und daher den globalen Bereich nicht wie in der reinen Assembly verschmutzt überlappende Schleifen haben ihren Platz und sind sehr verbreitet – wie im folgenden Code für 8051, wo die 7-Segment-Anzeige an P1 angeschlossen ist.Das Programm führt ein Blitzsegment in einer Schleife aus:

; P1 states loops
; 11111110 <-
; 11111101  |
; 11111011  |
; 11110111  |
; 11101111  |
; 11011111  |
; |_________|

init_roll_state:
    MOV P1,#11111110b
    ACALL delay
next_roll_state:
    MOV A,P1
    RL A
    MOV P1,A
    ACALL delay
    JNB P1.5, init_roll_state
    SJMP next_roll_state

Es gibt noch einen weiteren Vorteil:goto kann als benannte Schleifen, Bedingungen und andere Abläufe dienen:

if(valid) {
  do { // while(loop)

// more than one page of code here
// so it is better to comment the meaning
// of the corresponding curly bracket

  } while(loop);
} // if(valid)

Oder Sie können das entsprechende goto mit Einrückung verwenden, sodass Sie keinen Kommentar benötigen, wenn Sie den Labelnamen mit Bedacht wählen:

if(!valid) goto NOTVALID;
  LOOPBACK:

// more than one page of code here

  if(loop) goto LOOPBACK;
NOTVALID:;
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top