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


201

Ich habe lange den Eindruck gehabt, dass gotowenn möglich niemals verwendet werden sollte. Als ich neulich libavcodec (das in C geschrieben ist) durchgesehen habe, habe ich festgestellt, dass es mehrfach verwendet wird. Ist es jemals vorteilhaft, gotoin einer Sprache zu arbeiten, die Schleifen und Funktionen unterstützt? Wenn ja warum?

Antworten:


242

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

Funktion sauber verlassen

In einer Funktion können Sie häufig Ressourcen zuweisen und müssen an mehreren Stellen beendet werden. Programmierer können ihren Code vereinfachen, indem sie den Ressourcenbereinigungscode am Ende der Funktion einfügen, und alle "Austrittspunkte" der Funktion werden auf das Bereinigungsetikett gesetzt. 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 allen Schleifen ausbrechen müssen , kann ein goto dies viel sauberer und einfacher machen als break-Anweisungen und if-Checks.

Leistungsverbesserungen auf niedriger Ebene

Dies gilt nur für perf-kritischen Code, 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.


15
Der richtige Weg, um verschachtelte Schleifen zu verlassen, besteht darin, die innere Schleife in eine separate Methode umzugestalten.
Jason

129
@ Jason - Bah. Das ist eine Ladung Stier. Ersetzen gotodurch returnist einfach albern. Es wird nichts "umgestaltet", sondern nur "umbenannt", damit Menschen, die in einer gotounterdrückten Umgebung aufgewachsen sind (dh wir alle), sich besser fühlen, wenn sie das verwenden, was moralisch gleichbedeutend ist mit a goto. Ich sehe die Schleife lieber dort, wo ich sie benutze, und sehe ein wenig goto, was für sich genommen nur ein Werkzeug ist , als jemanden zu sehen, der die Schleife an einen Ort verschoben hat, der nichts damit zu tun hat, nur um a zu vermeiden goto.
Chris Lutz

18
Es gibt Situationen, in denen gotos wichtig sind: zum Beispiel eine ausnahmefreie C ++ - Umgebung. In der Silverlight-Quelle gibt es Zehntausende (oder mehr) von goto-Anweisungen für eine sichere Funktion, die mithilfe von Makros verwendet werden. Wichtige Mediencodecs und -bibliotheken arbeiten häufig mit Rückgabewerten und niemals mit Ausnahmen, und es ist schwierig, diese Fehlerbehandlungsmechanismen in zu kombinieren ein einziger performanter Weg.
Jeff Wilcox

79
Es ist erwähnenswert, dass alle break, continue, returnist im Grunde gotonur in schöner Verpackung.
el.pescado

16
Ich sehe nicht ein, wie do{....}while(0)eine bessere Idee als goto sein soll, außer dass es in Java funktioniert.
Jeremy List

906

Jeder, der gotodirekt oder indirekt gegen Edsger Dijkstras Artikel GoTo Considered Harmful spricht , begründet seine Position. Schade, dass Dijkstras Artikel praktisch nichts mit der Art gotound Weise zu tun hat, wie Aussagen heutzutage verwendet werden, und daher ist das, was der Artikel sagt, für die moderne Programmierszene kaum oder gar nicht anwendbar. Dasgoto -lose Mem grenzt jetzt an eine Religion, bis hin zu seinen Schriften, die von oben diktiert wurden, seinen Hohepriestern und dem Meiden (oder Schlimmeren) von wahrgenommenen Ketzern.

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

Als Dijkstra seine Arbeit schrieb, 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 Leute, die die höheren Sprachen verwendeten, in verdrehten, verzerrten Ausführungsthreads, die den Begriff "Spaghetti-Code" hervorbrachten, über ihre gesamte Codebasis sprangen. Sie können dies sehen, indem Sie zu dem klassischen Trek-Spiel von Mike Mayfield übergehen und versuchen, herauszufinden, wie die Dinge funktionieren. Nehmen Sie sich einen Moment Zeit, um das zu überprüfen.

Dies ist "die ungezügelte Verwendung der Go-to-Statement", gegen die Dijkstra 1968 in seiner Zeitung schimpfte . Dies ist die Umgebung, in der er lebte, die ihn dazu veranlasste, diese Zeitung zu schreiben. Die Fähigkeit, zu jedem beliebigen Zeitpunkt in Ihrem Code zu springen, war das, was er kritisierte und forderte, gestoppt zu werden. Ein Vergleich mit den anämischen Kräften gotoin C oder anderen moderneren Sprachen ist einfach riskant.

Ich kann bereits die erhobenen Gesänge der Kultisten hören, wenn sie sich dem Ketzer stellen. "Aber", werden sie singen, "Sie können es sehr schwierig machen, Code gotoin C zu lesen ." Oh ja? Sie können auch das Lesen von Code sehr schwierig machen goto. 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()
{
            _-_-_-_
       _-_-_-_-_-_-_-_-_
    _-_-_-_-_-_-_-_-_-_-_-_
  _-_-_-_-_-_-_-_-_-_-_-_-_-_
 _-_-_-_-_-_-_-_-_-_-_-_-_-_-_
 _-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
 _-_-_-_-_-_-_-_-_-_-_-_-_-_-_
 _-_-_-_-_-_-_-_-_-_-_-_-_-_-_
  _-_-_-_-_-_-_-_-_-_-_-_-_-_
    _-_-_-_-_-_-_-_-_-_-_-_
        _-_-_-_-_-_-_-_
            _-_-_-_
}

Nicht gotoin Sicht, also muss es leicht zu lesen sein, oder? Oder wie wäre es mit diesem:

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 auch gotodort. Es muss daher lesbar sein.

Was ist mein Punkt mit diesen Beispielen? Es sind keine Sprachfunktionen, die unlesbaren, nicht wartbaren Code machen. Es ist nicht die Syntax, die das macht. Es sind schlechte Programmierer, die das verursachen. Und schlechte Programmierer können, wie Sie in diesem Punkt sehen können, jede Sprachfunktion unlesbar und unbrauchbar machen. Wie die forSchleifen dort oben. (Sie können sie sehen, richtig?)

Um fair zu sein, sind einige Sprachkonstrukte leichter zu missbrauchen als andere. Wenn Sie jedoch ein C-Programmierer sind, würde ich ungefähr 50% der Verwendungszwecke viel genauer betrachten, #definelange bevor ich einen Kreuzzug gegen sie führen 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. Dijkstra-Papier auf gotoAussagen wurde für eine Programmierumgebung geschrieben , in dem gotowurde eine Menge mehr potenziell schädlich , als es in den meisten modernen Sprachen, die nicht ein Assembler sind.
  2. Das automatische Wegwerfen aller Verwendungszwecke gotoist ungefähr so ​​rational wie das Sprichwort "Ich habe einmal versucht, Spaß zu haben, aber es hat mir nicht gefallen, also bin ich jetzt dagegen".
  3. Es gibt legitime Verwendungen der modernen (anämischen) gotoAnweisungen im Code, die nicht angemessen durch andere Konstrukte ersetzt werden können.
  4. Es gibt natürlich unzulässige Verwendungen derselben Aussagen.
  5. Es gibt auch illegitime Verwendungen der modernen Kontrollanweisungen wie den " godo" Greuel, bei dem eine immer falsche doSchleife breakanstelle von a aus der Verwendung herausgebrochen wird goto. Diese sind oft schlimmer als vernünftiger Gebrauch goto.

42
@pocjoc: Schreiben einer rekursiven Optimierung zum Beispiel in C. Dies tritt bei der Implementierung von Dolmetschern / Laufzeiten für funktionale Sprachen auf, ist jedoch repräsentativ für eine interessante Kategorie von Problemen. Die meisten in C geschriebenen Compiler verwenden aus diesem Grund goto. Es ist eine Nischenverwendung, die in den meisten Situationen natürlich nicht vorkommt, aber in den Situationen, in denen sie benötigt wird, ist die Verwendung sehr sinnvoll.
zxq9

66
Warum reden alle über Dijkstras "Go to Considered schädliches" Papier, ohne Knuths Antwort darauf zu erwähnen: "Strukturierte Programmierung mit Go to Statements"?
Oblomov

22
@ Nietzche-jou "das ist ein ziemlich eklatanter Strohmann [...]" Er verwendet sarkastisch eine falsche Zweiteilung, um zu zeigen, warum das Stigma unlogisch ist. Ein Strawman ist ein logischer Irrtum, bei dem Sie die Position des Gegners absichtlich falsch darstellen, um den Eindruck zu erwecken, dass seine Position leicht besiegt werden kann. ZB "Atheisten sind nur Atheisten, weil sie Gott hassen". Eine falsche Zweiteilung liegt vor, wenn Sie annehmen, dass das entgegengesetzte Extrem wahr ist, wenn mindestens eine zusätzliche Möglichkeit besteht (dh Schwarzweiß). ZB "Gott hasst Homosexuelle, deshalb liebt er Heteros."
Braden Best

27
@JLRishe Du liest zu tief in etwas hinein, das nicht einmal da ist. Es ist nur ein wahrer logischer Irrtum, wenn die Person, die es verwendet hat, ehrlich glaubt, dass es logisch sinnvoll ist. Aber er sagte es sarkastisch und zeigte deutlich, dass er weiß, dass es lächerlich ist, und so zeigt er es. Er "benutzt es nicht, um seinen Standpunkt zu rechtfertigen"; er benutzt es, um zu zeigen, warum es lächerlich ist zu sagen, dass gotos automatisch Code in Spaghetti verwandeln. Dh du kannst immer noch beschissenen Code ohne gotos schreiben. DAS ist sein Punkt. Und sarkastische falsche Dichotomie ist seine Methode, sie auf humorvolle Weise zu veranschaulichen.
Braden Best

32
-1 für ein sehr großes Argument, warum Dijkstras Bemerkung nicht anwendbar ist, während vergessen wird, zu zeigen, was die Vorteile von gototatsächlich sind (was die gestellte Frage ist)
Maarten Bodewes

154

Das blinde Befolgen von Best Practices ist keine Best Practice. Die Idee, gotoAussagen als primäre Form der Flusskontrolle zu vermeiden, besteht darin, zu vermeiden, dass unlesbarer Spaghetti-Code erzeugt wird. Wenn sie an den richtigen Stellen sparsam eingesetzt werden, 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. Trotz der gotoAussagen ist sein Code immer noch perfekt lesbar.

Fazit: Vermeiden gotoum zu vermeiden gotoist sinnlos. Was Sie wirklich vermeiden möchten, ist das Erstellen von unlesbarem Code. Wenn Ihr gotogeladener Code lesbar ist, ist daran nichts auszusetzen.


36

Da gotoes schwierig ist, über den Programmfluss nachzudenken 1 (auch bekannt als „Spaghetti-Code“), gotowird dies im Allgemeinen nur verwendet, um fehlende Funktionen zu kompensieren: Die Verwendung von gotokann tatsächlich akzeptabel sein, aber nur, wenn die Sprache keine strukturiertere Variante bietet das gleiche Ziel. Nehmen Sie das Beispiel von Doubt:

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

Dies ist wahr - aber nur, wenn die Sprache keine strukturierte Ausnahmebehandlung mit Bereinigungscode (wie RAII oder finally) zulässt , die den gleichen Job besser erledigt (da sie speziell dafür entwickelt wurde), oder wenn es einen guten Grund dafür gibt strukturierte Ausnahmebehandlung anwenden (aber Sie werden diesen Fall nie haben, außer auf einer sehr niedrigen Ebene).

In den meisten anderen Sprachen besteht die einzig akzeptable Verwendung gotodarin, verschachtelte Schleifen zu beenden. Und selbst dort ist es fast immer besser, die äußere Schleife in eine eigene Methode zu heben und returnstattdessen zu verwenden.

Davon abgesehen gotoist dies ein Zeichen dafür, dass nicht genügend Gedanken in den jeweiligen Code eingeflossen sind.


1 Moderne Sprachen, die die gotoImplementierung einiger Einschränkungen unterstützen (z. B. gotonicht in Funktionen springen oder aus Funktionen herausspringen), aber das Problem bleibt im Wesentlichen dasselbe.

Dies gilt übrigens natürlich auch für andere Sprachmerkmale, insbesondere für Ausnahmen. In der Regel gelten strenge Regeln, um diese Funktionen nur dort zu verwenden, wo dies angegeben ist, z. B. die Regel, keine Ausnahmen zur Steuerung des nicht außergewöhnlichen Programmflusses zu verwenden.


4
Nur neugierig hier, aber was ist mit dem Fall, gotos für den Bereinigungscode zu verwenden? Mit Bereinigung meine ich nicht nur die Freigabe des Speichers, sondern auch die Fehlerprotokollierung. Ich habe eine Reihe von Posts gelesen und anscheinend schreibt niemand Code, der Protokolle druckt. Hmm?!
Shiva

37
"Grundsätzlich aufgrund der fehlerhaften Natur von goto (und ich glaube, dass dies unumstritten ist)" -1 und ich hörten dort auf zu lesen.
o0 '.

14
Die Ermahnung gegen goto stammt aus der Ära der strukturierten Programmierung, in der frühe Renditen ebenfalls als böse angesehen wurden. Das Verschieben verschachtelter Schleifen in eine Funktion tauscht ein "Übel" gegen ein anderes aus und erstellt eine Funktion, die keinen wirklichen Grund zur Existenz hat (außerhalb von "es erlaubte mir, die Verwendung eines goto zu vermeiden!"). Es ist wahr, dass goto das leistungsstärkste Tool zum Erstellen von Spaghetti-Code ist, wenn es ohne Einschränkungen verwendet wird, aber dies ist eine perfekte Anwendung dafür. es vereinfacht den Code. Knuth sprach sich für diese Verwendung aus und Dijkstra sagte: "Fallen Sie nicht in die Falle, zu glauben, dass ich in Bezug auf goto furchtbar dogmatisch bin."
Schlamm

5
finally? Die Verwendung von Ausnahmen für andere Dinge als die Fehlerbehandlung ist also gut, aber die Verwendung gotoist schlecht? Ich denke, Ausnahmen sind ziemlich treffend benannt.
Christian

3
@Mud: In den Fällen, denen ich (täglich) begegne, ist es die beste Lösung , eine Funktion zu erstellen, "die keinen wirklichen Grund zur Existenz hat" . Grund: Es werden Details aus der Funktion der obersten Ebene entfernt, sodass das Ergebnis einen leicht lesbaren Kontrollfluss aufweist. Ich persönlich begegne keinen Situationen, in denen ein goto den am besten lesbaren Code erzeugt. Auf der anderen Seite verwende ich mehrere Rückgaben in einfachen Funktionen, bei denen jemand anderes es vorziehen könnte, zu einem einzelnen Austrittspunkt zu gelangen. Ich würde gerne Gegenbeispiele sehen, bei denen goto besser lesbar ist als das Refactoring in gut benannte Funktionen.
ToolmakerSteve

35

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

2
do{}while(false)Ich denke, kann als idiomatisch angesehen werden. Sie dürfen nicht widersprechen: D
Thomas Eding

37
@trinithis: Wenn es "idiomatisch" ist, liegt das nur am Anti-Goto-Kult. Wenn Sie es sich genau ansehen, werden Sie feststellen, dass es nur eine Art zu sagen ist, goto after_do_block;ohne das tatsächlich zu sagen. Sonst ... eine "Schleife", die genau einmal läuft? Ich würde das Missbrauch von Kontrollstrukturen nennen.
CHao

5
@ThomasEding Eding Es gibt eine Ausnahme zu Ihrem Punkt. Wenn Sie jemals C / C ++ programmiert haben und #define verwenden mussten, wissen Sie, dass do {} while (0) einer der Standards für die Kapselung mehrerer Codezeilen ist. Zum Beispiel: #define do {memcpy (a, b, 1); etwas ++;} während (0) sicherer und besser ist als #define memcpy (a, b, 1); etwas ++
Ignas2526

10
@ Ignas2526 Sie haben gerade sehr schön gezeigt, wie #defineviele, viele Male viel schlimmer sind als ab und zu zu verwenden goto: D
Luaan

3
+1 für eine gute Liste von Möglichkeiten, wie Menschen versuchen, Gotos bis zum Äußersten zu vermeiden; Ich habe anderswo Erwähnungen solcher Techniken gesehen, aber dies ist eine großartige, prägnante Liste. Ich überlege, warum mein Team in den letzten 15 Jahren keine dieser Techniken benötigt oder Gotos verwendet hat. Selbst in einer Client / Server-Anwendung mit Hunderttausenden von Codezeilen von mehreren Programmierern. C #, Java, PHP, Python, Javascript. Client-, Server- und App-Code. Nicht prahlen oder für eine Position werben, nur wirklich neugierig, warum manche Menschen auf Situationen stoßen, die nach Gotos als klarste Lösung verlangen, und andere nicht ...
ToolmakerSteve

28

In C # Schalter Aussage erlauben doest nicht durchsturz . Daher wird goto verwendet, um die Steuerung auf ein bestimmtes Switch-Case-Label oder das Standard- Label zu übertragen.

Beispielsweise:

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 Regel "Kein Fall-Through". Fall-through ist zulässig, wenn eine case-Anweisung keinen Code enthält.


3
Switch Fall-Through wird in .NET 2.0 unterstützt - msdn.microsoft.com/en-us/library/06tc147t(VS.80).aspx
rjzii

9
Nur wenn der Fall keinen Code-Body hat. Wenn es Code enthält, müssen Sie das Schlüsselwort goto verwenden.
Matthew Whited

26
Diese Antwort ist so lustig - C # hat den Durchfall entfernt, weil viele ihn als schädlich ansehen, und in diesem Beispiel wird goto (von vielen auch als schädlich angesehen) verwendet, um das ursprüngliche, angeblich schädliche Verhalten wiederzubeleben, ABER das Gesamtergebnis ist tatsächlich weniger schädlich ( weil der Code klar macht, dass der Fallthrough beabsichtigt ist!).
Thomasrutter

9
Nur weil ein Schlüsselwort mit den Buchstaben GOTO geschrieben ist, ist es kein Goto. Dieser vorgestellte Fall ist kein Goto. Es ist ein switch-Anweisungskonstrukt für fallthrough. Andererseits kenne ich C # nicht wirklich gut, also könnte ich mich irren.
Thomas Eding

1
Nun, in diesem Fall ist es ein bisschen mehr als ein Durchfall (weil Sie sagen können, goto case 5:wann Sie in Fall 1 sind). Es scheint, als ob Konrad Rudolphs Antwort hier richtig ist: Sie gotokompensiert ein fehlendes Merkmal (und ist weniger klar als das eigentliche Merkmal). Wenn wir wirklich einen Durchfall wollen, wäre der vielleicht beste Standard kein Durchfall, sondern continueeine explizite Anforderung.
David Stone

14

#ifdef TONGUE_IN_CHEEK

Perl hat eine goto, mit der Sie die Tail Calls von Armen implementieren können. :-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 Cs zu tun goto. Im Ernst, ich stimme den anderen Kommentaren zur Verwendung gotofür Bereinigungen oder zur Implementierung von Duffs Gerät oder dergleichen zu. Es geht nur darum zu benutzen, nicht zu missbrauchen.

(Der gleiche Kommentar kann für longjmpAusnahmen call/ccund dergleichen gelten - sie haben legitime Verwendungszwecke, können jedoch leicht missbraucht werden. Beispielsweise kann eine Ausnahme nur ausgelöst werden, um einer tief verschachtelten Kontrollstruktur unter völlig nicht außergewöhnlichen Umständen zu entgehen .)


Ich denke, dies ist der einzige Grund, goto in Perl zu verwenden.
Brad Gilbert

12

Ich habe im Laufe der Jahre mehr als ein paar Zeilen Assemblersprache geschrieben. Letztendlich kompiliert sich jede Hochsprache zu gotos. Okay, nenne sie "Zweige" oder "Sprünge" oder was auch immer, aber sie sind gotos. Kann jemand einen Assembler ohne Goto schreiben?

Jetzt können Sie sicher einen Fortran-, C- oder BASIC-Programmierer darauf hinweisen, dass es ein Rezept für Spaghetti Bolognese ist, mit Gotos aufzuregen. Die Antwort ist jedoch nicht, sie zu vermeiden, sondern sie vorsichtig zu verwenden.

Ein Messer kann verwendet werden, um Essen zuzubereiten, jemanden zu befreien oder jemanden zu töten. Verzichten wir aus Angst vor letzteren auf Messer? Ebenso das goto: unachtsam eingesetzt behindert es, vorsichtig eingesetzt hilft es.


1
Vielleicht möchten Sie unter stackoverflow.com/questions/46586/…
Konrad Rudolph

15
Jeder, der das "alles kompiliert sich sowieso zu JMP!" Das Argument versteht den Grund für die Programmierung in einer höheren Sprache grundsätzlich nicht.
Nietzche-jou

7
Alles, was Sie wirklich brauchen, ist Subtrahieren und Verzweigen. Alles andere dient der Bequemlichkeit oder Leistung.
David Stone

8

Werfen Sie einen Blick auf die Verwendung von Springen beim Programmieren in C :

Obwohl die Verwendung von goto fast immer eine schlechte Programmierpraxis ist (sicherlich können Sie einen besseren Weg finden, XYZ zu machen), gibt es Zeiten, in denen es wirklich keine schlechte Wahl ist. Einige 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 wirklich nur für C. Wenn Sie C ++ verwenden, gibt es keinen guten Grund, goto anstelle von Ausnahmen zu verwenden. In C verfügen Sie jedoch nicht über die Leistung 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 folgendermaßen 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];
}

Dies ist in Ordnung, bis Sie feststellen, dass Sie Ihren Bereinigungscode ändern müssen. Dann müssen Sie 4 Änderungen vornehmen. Jetzt können Sie entscheiden, dass Sie die gesamte Bereinigung in einer einzigen Funktion zusammenfassen können. Das ist keine schlechte Idee. Dies bedeutet jedoch, dass Sie mit Zeigern vorsichtig sein müssen. Wenn Sie einen Zeiger in Ihrer Bereinigungsfunktion freigeben möchten, können Sie ihn nicht so einstellen, dass er auf NULL zeigt, es sei denn, Sie übergeben einen Zeiger auf 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 ein anderes Element hinzufügen, das bereinigt werden muss, müssen Sie Ihre Bereinigungsfunktion erneut ändern. und dann müssen Sie die Argumente für diese Funktion ändern.

Mit gotowird es 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 für die Bereinigung erforderlich ist, und Sie es geschafft haben, die Anzahl der Änderungspunkte erheblich zu reduzieren. Ein weiterer Vorteil ist, dass Sie nicht mehr mehrere Ausstiegspunkte für Ihre Funktion haben, sondern nur noch einen. Es besteht keine Möglichkeit, dass Sie versehentlich von der Funktion zurückkehren, ohne aufzuräumen.

Da goto nur zum Springen zu einem einzelnen Punkt verwendet wird, ist es nicht so, als würden Sie eine Menge Spaghetti-Code erstellen, der hin und her springt, um Funktionsaufrufe zu simulieren. Vielmehr hilft goto tatsächlich dabei, strukturierteren Code zu schreiben.


Mit einem Wort, gotosollte immer sparsam und als letztes Mittel eingesetzt werden - aber es gibt eine Zeit und einen Ort dafür. Die Frage sollte nicht "Müssen Sie es verwenden" sein, sondern "Ist es die beste Wahl", um es zu verwenden.


7

Einer der Gründe, warum goto schlecht ist, ist neben dem Codierungsstil, dass Sie damit überlappende , aber nicht verschachtelte Schleifen erstellen können :

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

Dies würde die bizarre, aber möglicherweise legale Kontrollflussstruktur erzeugen, in der eine Sequenz wie (a, b, c, b, a, b, a, b, ...) möglich ist, was Compiler-Hacker unglücklich macht. Anscheinend gibt es eine Reihe cleverer Optimierungstricks, die darauf beruhen, dass diese Art von Struktur nicht auftritt. (Ich sollte meine Kopie des Drachenbuchs überprüfen ...) Dies könnte (unter Verwendung einiger Compiler) dazu führen, dass andere Optimierungen für Code, der gotos enthält, nicht durchgeführt werden .

Es kann nützlich sein, wenn Sie wissen , dass "oh, übrigens" den Compiler davon überzeugt, 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 auch versuchen, gotobevor ich den Assembler hacke.


3
Nun ... führt mich zurück zu den Tagen der Programmierung von FORTRAN in einem Finanzinformationsunternehmen. In 2007.
Marcin

5
Jede Sprachstruktur kann auf eine Weise missbraucht werden, die sie unlesbar macht oder eine schlechte Leistung erbringt.
NUR MEINE RICHTIGE MEINUNG

@JUST: Es geht nicht um Lesbarkeit oder schlechte Leistung, sondern um Annahmen und Garantien für das Kontrollflussdiagramm. Jegliche Missbrauch von goto würde für sein erhöhte die Leistung (oder Lesbarkeit).
Anders Eurenius

3
Ich würde behaupten, einer der Gründe, gotodie nützlich sind, ist, dass Sie damit Schleifen wie diese erstellen können, die ansonsten eine Reihe logischer Verzerrungen erfordern würden. Ich würde weiter argumentieren, dass wenn der Optimierer nicht weiß, wie man das umschreibt, dann gut . Eine solche Schleife sollte nicht aus Gründen der Leistung oder Lesbarkeit durchgeführt werden, sondern weil dies genau die Reihenfolge ist, in der die Dinge geschehen müssen. In diesem Fall möchte ich nicht, dass der Optimierer damit herumschraubt.
cHao

1
... eine einfache Übersetzung des erforderlichen Algorithmus in GOTO-basierten Code ist möglicherweise viel einfacher zu validieren als ein Durcheinander von Flag-Variablen und missbrauchten Schleifenkonstrukten.
Supercat

7

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 nicht akzeptabel sind. Denken 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 sich auf die Leistung, aber nicht auf die Richtigkeit auswirkt).

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 dies, bis zu drei Durchgänge durch einen Eimer zu machen, wie folgt:

// 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

Wenn ich goto nicht verwenden würde, wie würde dieser Code aussehen?

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 schlechter aussehen, wenn mehr Durchgänge hinzugefügt würden, während die Version mit goto jederzeit die gleiche Einrückungsstufe beibehält und die Verwendung von falschen if-Anweisungen vermeidet, deren Ergebnis durch die Ausführung der vorherigen Schleife impliziert wird.

Es gibt also einen anderen Fall, in dem goto den Code sauberer und einfacher zu schreiben und zu verstehen macht ... Ich bin sicher, es gibt noch viel mehr. Geben Sie also nicht vor, alle Fälle zu kennen, in denen goto nützlich ist, und lehnen Sie alle guten ab, die Sie nicht könnten Ich denke nicht daran.


1
In dem Beispiel, das Sie gegeben haben, würde ich es vorziehen, das ziemlich deutlich umzugestalten. Im Allgemeinen versuche ich, einzeilige Kommentare zu vermeiden, die besagen, was der nächste Codeabschnitt tut. Stattdessen zerlege ich das in eine eigene Funktion, die ähnlich wie der Kommentar benannt ist. Wenn Sie eine solche Transformation durchführen würden, würde diese Funktion einen allgemeinen Überblick über die Funktionsweise der Funktion geben, und jede der neuen Funktionen würde angeben, wie Sie die einzelnen Schritte ausführen. Ich halte es für viel wichtiger als jede Opposition, gotodass jede Funktion auf derselben Abstraktionsebene liegt. Dass es vermeidet, gotoist ein Bonus.
David Stone

2
Sie haben nicht wirklich erklärt, wie das Hinzufügen weiterer Funktionen das goto, die mehrfachen Einrückungsstufen und falschen if-Anweisungen beseitigt ...
Ricardo

Mit der Standard-Containernotation würde es ungefähr so ​​aussehen: container::iterator it = slot_p.find(hash_key); if (it != slot_p.end()) it->overwrite(hash_key); else it = slot_p.find_first_empty();Ich finde diese Art der Programmierung viel einfacher zu lesen. Jede Funktion könnte in diesem Fall als reine Funktion geschrieben werden, über die viel einfacher nachzudenken ist. Die Hauptfunktion erklärt nun anhand des Namens der Funktionen, was der Code tut. Wenn Sie möchten, können Sie anhand der Definitionen herausfinden, wie er funktioniert.
David Stone

3
Die Tatsache, dass jeder Beispiele geben muss, wie bestimmte Algorithmen natürlich ein goto verwenden sollten - ist eine traurige Reflexion darüber, wie wenig algorithmisches Denken heute weitergeht !! Natürlich ist @ Ricardos Beispiel (eines von vielen) ein perfektes Beispiel dafür, wo goto elegant und offensichtlich ist.
Fattie

6

Die Regel bei goto, die wir verwenden, lautet, dass goto in Ordnung ist, um vorwärts zu einem einzelnen Exit-Bereinigungspunkt in einer Funktion zu springen. In wirklich komplexen Funktionen lockern wir diese Regel, um andere Vorwärtssprünge zu ermöglichen. In beiden Fällen vermeiden wir tief verschachtelte Anweisungen, die häufig bei der Fehlercodeprüfung auftreten, was die Lesbarkeit und Wartung verbessert.


1
Ich könnte sehen, dass so etwas in einer Sprache wie C nützlich ist. Wenn Sie jedoch die Leistung von C ++ - Konstruktoren / Destruktoren haben, ist dies im Allgemeinen nicht so nützlich.
David Stone

1
"In wirklich komplexen Funktionen lockern wir diese Regel, um andere Sprünge vorwärts zu ermöglichen." Ohne ein Beispiel klingt dies so, als würden Sie komplexe Funktionen durch Sprünge noch komplexer machen. Wäre es nicht der "bessere" Ansatz, diese komplexen Funktionen umzugestalten und aufzuteilen?
MikeMB

1
Dies war der letzte Zweck, für den ich goto verwendet habe. Ich vermisse goto in Java nicht, weil es try-finally gibt, das den gleichen Job machen kann.
Patricia Shanahan

5

Die nachdenklichste und gründlichste Diskussion über goto-Anweisungen, ihre legitimen Verwendungen und alternativen Konstrukte, 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 dieses 39 Jahre alten Papiers datiert sind: Durch die Erhöhung der Verarbeitungsleistung um Größenordnungen werden einige der Leistungsverbesserungen von Knuth bei Problemen mittlerer Größe nicht bemerkt, und seitdem wurden neue Konstrukte für Programmiersprachen erfunden. (Zum Beispiel fassen Try-Catch-Blöcke Zahns Konstrukt zusammen, obwohl sie selten auf diese Weise verwendet werden.) Knuth deckt jedoch alle Seiten des Arguments ab und sollte gelesen werden müssen, bevor jemand das Problem erneut aufgreift.


3

In einem Perl-Modul möchten Sie gelegentlich Unterprogramme oder Abschlüsse im laufenden Betrieb erstellen. Die Sache ist, dass, sobald Sie die Unterroutine erstellt haben, wie Sie dazu kommen. Sie könnten es einfach aufrufen, aber wenn das Unterprogramm es verwendet, ist caller()es nicht so hilfreich wie es sein könnte. Hier kann die goto &subroutineVariation 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 diese Form auch 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 wäre das besser geschrieben als goto __SUB__;)

Es gibt ein Modul, das einen tailModifikator importiert, und eines, das importiert wird, recurwenn Sie diese Form von nicht mögen 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 lassen gotosich besser mit anderen Schlüsselwörtern erledigen.

Wie redoein bisschen Code:

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

Oder gehen Sie lastvon mehreren Stellen zum Code?

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

2

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 tragen wesentlich dazu bei, diese Mängel zu beheben.

Manchmal ist es klarer, eine Flag-Variable zu verwenden, um eine Art Pseudo-Multi-Level-Break zu bewirken, aber sie ist dem goto nicht immer überlegen (zumindest kann man mit goto im Gegensatz zu einer Flag-Variablen leicht bestimmen, wohin die Steuerung geht ), und manchmal möchten Sie einfach nicht den Leistungspreis von Flaggen / anderen Verrenkungen bezahlen, um das Goto zu vermeiden.

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


2

Genauso gut hat niemand jemals die Anweisung "COME FROM" implementiert ....


8
Oder in C ++, C #, Java, JS, Python, Ruby ... usw. usw. usw. Nur sie nennen es "Ausnahmen".
CHao

2

Ich finde das do {} während (falscher) Gebrauch absolut abstoßend. Es ist denkbar, mich davon zu überzeugen, dass es in einem seltsamen Fall notwendig ist, aber niemals, dass es sich um sauberen, vernünftigen Code handelt.

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

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

Sollte das nicht /*empty*/sein stepfailed = 1? Wie ist das auf jeden Fall besser als ein do{}while(0)? In beiden Fällen müssen Sie breaksich davon lösen (oder von Ihnen stepfailed = 1; continue;). Scheint mir unnötig.
Thomas Eding

2

1) Die häufigste Verwendung von goto, die ich kenne, ist die Emulation der Ausnahmebehandlung in Sprachen, die sie nicht anbieten, nämlich in C. (Der von Nuclear oben angegebene Code ist genau das.) Schauen Sie sich den Linux-Quellcode an und Sie ' Ich werde eine Unmenge von Gotos sehen, die auf diese Weise verwendet werden. Laut einer 2013 durchgeführten Kurzumfrage gab es im Linux-Code etwa 100.000 Gotos: http://blog.regehr.org/archives/894 . Die Verwendung von Goto wird sogar im Linux-Codierungsstil-Handbuch erwähnt: https://www.kernel.org/doc/Documentation/CodingStyle . So wie die objektorientierte Programmierung mit Strukturen emuliert wird, die mit Funktionszeigern gefüllt sind, hat goto seinen Platz in der C-Programmierung. Wer hat Recht: Dijkstra oder Linus (und alle Linux-Kernel-Codierer)? Im Grunde ist es Theorie gegen Praxis.

Es gibt jedoch das übliche Problem, keine Unterstützung auf Compilerebene zu haben und nach gängigen Konstrukten / Mustern zu suchen: Es ist einfacher, sie falsch zu verwenden und Fehler ohne Überprüfungen zur Kompilierungszeit einzuführen. Windows und Visual C ++, jedoch im C-Modus, bieten aus genau diesem Grund eine Ausnahmebehandlung über SEH / VEH: Ausnahmen sind auch außerhalb von OOP-Sprachen nützlich, dh in einer prozeduralen Sprache. Der Compiler kann Ihren Speck jedoch nicht immer speichern, 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-Fehler "goto fail", der nur einen goto mit katastrophalen Folgen duplizierte ( 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

Mit vom Compiler unterstützten Ausnahmen können Sie genau denselben Fehler haben, z. B. 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 Sie 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 gefunden werden kann!) Aus einem ziemlich guten Grund: Es ist wahrscheinlich ein Fehler im durchschnittlichen Joe-Code. Solange das goto-Konstrukt keine Ziele zulässt, die der Compiler nicht leicht herausfinden kann, wie gotos zu berechneten Adressen (**), ist es für den Compiler nicht schwieriger, nicht erreichbaren Code in einer Funktion mit gotos zu finden, als Dijkstra zu verwenden -genehmigter Code.

(**) Fußnote: In einigen Versionen von Basic sind Gotos zu berechneten Zeilennummern möglich, z. B. GOTO 10 * x, wobei x eine Variable ist. Eher verwirrend bezieht sich in Fortran "computed goto" auf ein Konstrukt, das einer switch-Anweisung in C entspricht. Standard C erlaubt keine berechneten gotos in der Sprache, sondern nur gotos für statisch / syntaktisch deklarierte Labels. GNU C hat jedoch eine Erweiterung, um die Adresse eines Labels abzurufen (der unäre Operator, das Präfix &&) und ermöglicht auch das Wechseln zu einer Variablen vom Typ void *. Weitere Informationen zu diesem obskuren Unterthema finden Sie unter https://gcc.gnu.org/onlinedocs/gcc/Labels-as-Values.html . Der Rest dieses Beitrags befasst sich nicht mit dieser obskuren GNU C-Funktion.

Standard-C-Gotos (dh nicht berechnete Gotos) sind normalerweise nicht der Grund, warum nicht erreichbarer Code zur Kompilierungszeit nicht gefunden werden kann. Der übliche Grund ist Logikcode wie der folgende. Gegeben

int computation1() {
  return 1;
}

int computation2() {
  return computation1();
}

Für einen Compiler ist es genauso schwierig, nicht erreichbaren Code in einem der folgenden 3 Konstrukte 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 Klammer-bezogenen Codierungsstil, aber ich habe versucht, die Beispiele so kompakt wie möglich zu halten.)

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

Als verwandtes Problem kann das C goto verwendet werden, um Ausnahmen nur innerhalb des Körpers einer Funktion zu emulieren. Die Standard-C-Bibliothek bietet ein Funktionspaar setjmp () und longjmp () zum Emulieren nicht lokaler Exits / Ausnahmen, die jedoch im Vergleich zu anderen Sprachen einige schwerwiegende Nachteile aufweisen. 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 verwendet 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 einer mehrstufigen Unterbrechung oder einer mehrstufigen Fortsetzung, was ebenfalls ein ziemlich unumstrittener Anwendungsfall ist. Denken Sie daran, dass Java kein Goto-Label zulässt, aber Break-Label zulässt oder Label fortsetzt. Laut http://www.oracle.com/technetwork/java/simple-142616.html ist dies tatsächlich der häufigste Anwendungsfall von gotos in C (90% sagen sie), aber meiner subjektiven Erfahrung nach tendiert der Systemcode dazu um gotos häufiger für die Fehlerbehandlung zu verwenden. Vielleicht in wissenschaftlichem Code oder wenn das Betriebssystem Ausnahmebehandlung (Windows) bietet, sind mehrstufige Exits der dominierende Anwendungsfall. Sie geben keine genauen Angaben zum Kontext ihrer Umfrage.

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


Richtig. Der "Elefant im Raum" ist, dass der Linux-Kernel, um Himmels willen, als ein Beispiel für eine weltkritische Codebasis - mit goto geladen ist. Natürlich ist es das. Offensichtlich. Das "Anti-Goto-Mem" ist nur eine Kuriosität von vor Jahrzehnten. Natürlich gibt es eine Reihe von Dingen in der Programmierung (insbesondere "Statik", in der Tat "Globale" und zum Beispiel "elseif"), die von Laien missbraucht werden können. Also, wenn Ihr Cousin Cousin in Programm 2 lernt, sagen Sie ihnen "Oh, benutze keine Globals" und "benutze niemals elseif".
Fattie

Der Fehler goto fail hat nichts mit goto zu tun. Das Problem wird durch die if-Anweisung ohne geschweifte Klammern verursacht. Nahezu jede zweimal eingefügte Anweisungskopie hätte ein Problem verursacht. Ich halte diese Art von nacktem Braceless für viel schädlicher als goto.
Muusbolla

2

Einige sagen, dass es in C ++ keinen Grund gibt, zu gehen. Einige sagen, dass es in 99% der Fälle bessere Alternativen gibt. Dies ist keine Argumentation, sondern nur irrationale Eindrücke. Hier ist ein solides Beispiel, in 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-freiem 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:

  • verschachtelter {}Block wird benötigt (obwohl do {...} whileer vertrauter aussieht)
  • Es wird eine zusätzliche loopVariable benötigt, die an vier Stellen verwendet wird
  • Das Lesen und Verstehen der Arbeit mit dem dauert länger loop
  • Das loopenthält keine Daten, sondern steuert lediglich den Ablauf der Ausführung, der weniger verständlich ist als ein einfaches Label

Es gibt noch ein anderes 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?
  }
}

Lassen Sie uns jetzt das "böse" loswerden:

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 ist die gleiche Art der Verwendung von goto, es ist ein gut strukturiertes Muster und es ist nicht vorwärts gehen, um so viele zu fördern, wie der einzig empfohlene Weg. 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. Beachten Sie, dass label in C ++ einen Funktionsumfang hat, sodass es den globalen Bereich nicht wie in einer reinen Assembly verschmutzt, in der überlappende Schleifen ihren Platz haben und sehr häufig sind - wie im folgenden Code für 8051, in dem die 7-Segment-Anzeige mit P1 verbunden ist. Das Programm schleift ein Blitzsegment um:

; 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 Flows 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 ein gleichwertiges goto mit Einrückung verwenden, sodass Sie keinen Kommentar benötigen, wenn Sie den Markennamen mit Bedacht auswählen:

if(!valid) goto NOTVALID;
  LOOPBACK:

// more than one page of code here

  if(loop) goto LOOPBACK;
NOTVALID:;

1

Verwenden Sie in Perl ein Label, um aus einer Schleife zu "gehen" - mit einer "last" -Anweisung, die break ähnelt.

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

Das traditionelle goto- Label wird ebenfalls unterstützt, aber ich bin mir nicht sicher, ob es zu viele Fälle gibt, in denen dies der einzige Weg ist, um das zu erreichen, was Sie wollen - Unterprogramme und Schleifen sollten in den meisten Fällen ausreichen.


Ich denke, die einzige Form von goto, die Sie jemals in Perl verwenden würden, ist goto &subroutine. Womit das Unterprogramm mit dem aktuellen @_ gestartet wird, während das aktuelle Unterprogramm im Stapel ersetzt wird.
Brad Gilbert

1

Das Problem mit 'goto' und das wichtigste Argument der 'goto-less-Programmierung' ist, dass Ihr Code, wenn Sie ihn zu häufig verwenden, obwohl er sich möglicherweise richtig verhält, unlesbar, nicht wartbar, nicht überprüfbar usw. wird. In 99,99% von Die Fälle 'goto' führen zu Spaghetti-Code. Persönlich kann ich mir keinen guten Grund vorstellen, warum ich 'goto' verwenden würde.


11
Die Aussage "Ich kann mir keinen Grund vorstellen" in Ihrer Argumentation ist formal en.wikipedia.org/wiki/Argument_from_ignorance , obwohl ich die Terminologie "Beweis durch mangelnde Vorstellungskraft" bevorzuge.
NUR MEINE RICHTIGE MEINUNG

2
@NUR MEINE korrekte MEINUNG: Es ist nur dann ein logischer Irrtum, wenn es post-hoc angewendet wird. Aus Sicht eines Sprachdesigners kann es ein gültiges Argument sein, die Kosten für die Aufnahme eines Features ( goto) abzuwägen . Die Verwendung von @ cschol ist ähnlich: Während er momentan vielleicht keine Sprache entwirft, bewertet er im Grunde die Bemühungen des Designers.
Konrad Rudolph

1
@KonradRudolph: IMHO gotoist es billiger , eine Sprache zuzulassen, außer in Kontexten, in denen Variablen entstehen würden, als zu versuchen, jede Art von Kontrollstruktur zu unterstützen, die jemand benötigt. Das Schreiben von Code mit ist gotomöglicherweise nicht so gut wie das Verwenden einer anderen Struktur, aber wenn Sie solchen Code mit schreiben können, gotovermeiden Sie "Lücken in der Ausdruckskraft" - Konstrukte, für die eine Sprache keinen effizienten Code schreiben kann.
Supercat

1
@supercat Ich fürchte, wir kommen aus grundlegend verschiedenen Schulen des Sprachdesigns. Ich lehne es ab, dass Sprachen zum Preis der Verständlichkeit (oder Korrektheit) maximal ausdrucksstark sind. Ich hätte lieber eine restriktive als eine freizügige Sprache.
Konrad Rudolph

1
@thb Ja natürlich. Es macht es oft viel schwieriger, Code zu lesen und darüber nachzudenken. Praktisch jedes Mal, wenn jemand Code gotoauf der gotoCodeüberprüfungsseite veröffentlicht, vereinfacht das Eliminieren des Codes die Logik des Codes erheblich.
Konrad Rudolph

1

Das GOTO kann natürlich verwendet werden, aber es gibt eine wichtigere Sache als den Codestil oder 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 denke nach .

Schauen Sie sich zum Beispiel die folgenden zwei Codefragmente 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: 0" ist (wir nehmen natürlich eine Ausführung ohne Parallelität an).

Das ist nicht richtig: In der ersten Stichprobe ist A immer 0, in der zweiten Stichprobe (mit der GOTO-Anweisung) ist A möglicherweise nicht 0. Warum?

Der Grund ist, dass ich von einem anderen Punkt des Programms aus ein einfügen kann, GOTO FINALohne den Wert von A zu steuern.

Dieses Beispiel ist sehr offensichtlich, aber wenn Programme komplizierter werden, steigt die Schwierigkeit, solche Dinge zu sehen.

Zugehöriges Material findet sich in dem berühmten Artikel von Herrn Dijkstra "Ein Fall gegen die GO TO-Erklärung".


6
Dies war im Old-School-BASIC häufig der Fall. In modernen Varianten dürfen Sie jedoch nicht in die Mitte einer anderen Funktion springen ... oder in vielen Fällen sogar nach der Deklaration einer Variablen. Grundsätzlich (Wortspiel nicht beabsichtigt) haben moderne Sprachen die "ungezügelten" GOTOs, von denen Dijkstra sprach, weitgehend beseitigt ... und die einzige Möglichkeit, sie zu verwenden, während er dagegen argumentierte, besteht darin, bestimmte andere abscheuliche Sünden zu begehen. :)
CHao

1

Ich verwende goto im folgenden Fall: Wenn es erforderlich ist, von Funktionen an verschiedenen Orten zurückzukehren, und bevor es zurückkehrt, muss eine Uninitialisierung durchgeführt werden:

Nicht-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 an den Freigabeanweisungen ändern müssen (jede wird einmal im Code verwendet), und verringert die Wahrscheinlichkeit, eine dieser Anweisungen zu überspringen, wenn Sie einen neuen Zweig hinzufügen. Das Verschieben in eine Funktion hilft hier nicht weiter, da die Freigabe auf verschiedenen "Ebenen" erfolgen kann.


3
Deshalb haben wir finallyBlöcke in C #
John Saunders

^ wie @JohnSaunders sagte. Dies ist ein Beispiel für "Verwenden von goto, weil einer Sprache das entsprechende Steuerungskonstrukt fehlt". Es ist jedoch ein Code-Geruch, mehrere Punkte in der Nähe der Rückgabe zu benötigen. Es gibt einen Programmierstil, der sicherer ist (einfacher nicht zu vermasseln ) UND keine gotos erfordert, was auch in Sprachen ohne "endgültig" gut funktioniert: Entwerfen Sie diese "Bereinigungs" -Aufrufe so, dass sie harmlos sind, wenn keine Bereinigung erfolgt up ist erforderlich. Berücksichtigen Sie alles außer der Bereinigung in einer Methode, die das Multiple-Return-Design verwendet. Rufen Sie diese Methode auf und führen Sie dann die Bereinigungsaufrufe durch.
ToolmakerSteve

Beachten Sie, dass der von mir beschriebene Ansatz einen zusätzlichen Methodenaufruf erfordert (jedoch nur in einer Sprache, die fehlt finally). Verwenden Sie alternativ gotos, aber zu einem gemeinsamen Austrittspunkt, der immer die gesamte Bereinigung durchführt. Jede Bereinigungsmethode kann jedoch entweder einen Wert verarbeiten, der null ist oder bereits bereinigt ist, oder sie wird durch einen bedingten Test geschützt, der übersprungen wird, wenn dies nicht angemessen ist.
ToolmakerSteve

@ToolmakerSteve Dies ist kein Code-Geruch. Tatsächlich ist es ein äußerst verbreitetes Muster in C und wird als eine der gültigsten Methoden zur Verwendung von goto angesehen. Sie möchten, dass ich 5 Methoden erstelle, von denen jede ihren eigenen unnötigen if-Test hat, um die Bereinigung dieser Funktion durchzuführen? Sie haben jetzt Code AND Performance Bloat erstellt. Oder Sie könnten einfach goto verwenden.
Muusbolla

@muusbolla - 1) "5 Methoden erstellen" - Nein. Ich schlage eine einzelne Methode vor, die alle Ressourcen bereinigt, die einen Wert ungleich Null haben. ODER sehen Sie meine Alternative, die gotos verwendet, die alle zum gleichen Austrittspunkt gehen, der die gleiche Logik hat (was, wie Sie sagen, ein zusätzliches if pro Ressource erfordert). Aber Cegal , wenn Sie Recht haben - was auch immer der Grund ist, warum der Code in C ist, es ist mit ziemlicher Sicherheit ein Kompromiss, der den "direktesten" Code bevorzugt. (Mein Vorschlag behandelt komplexe Situationen, in denen eine bestimmte Ressource möglicherweise zugewiesen wurde oder nicht. Aber ja, in diesem Fall übertrieben.)
ToolmakerSteve

0

Edsger Dijkstra, ein Informatiker, der wichtige Beiträge auf diesem Gebiet leistete, war auch dafür bekannt, die Verwendung von GoTo zu kritisieren. Es gibt einen kurzen Artikel über seine Argumentation auf Wikipedia .


0

Es ist praktisch für die zeichenweise Zeichenfolgenverarbeitung von Zeit zu Zeit.

Stellen Sie sich so etwas wie dieses printf-artige Beispiel vor:

for cur_char, next_char in sliding_window(input_string) {
    if cur_char == '%' {
        if next_char == '%' {
            cur_char_index += 1
            goto handle_literal
        }
        # Some additional logic
        if chars_should_be_handled_literally() {
            goto handle_literal
        }
        # Handle the format
    }
    # some other control characters
    else {
      handle_literal:
        # Complicated logic here
        # Maybe it's writing to an array for some OpenGL calls later or something,
        # all while modifying a bunch of local variables declared outside the loop
    }
}

Sie könnten dies goto handle_literalin einen Funktionsaufruf umgestalten , aber wenn mehrere verschiedene lokale Variablen geändert werden, müssten Sie Verweise auf jede Variable übergeben, es sei denn, Ihre Sprache unterstützt veränderbare Abschlüsse. Sie continuemüssten nach dem Aufruf immer noch eine Anweisung verwenden (was wohl eine Form von goto ist), um dieselbe Semantik zu erhalten, wenn Ihre Logik dafür sorgt, dass ein anderer Fall nicht funktioniert.

Ich habe gotos auch in Lexern mit Bedacht eingesetzt, normalerweise für ähnliche Fälle. Sie brauchen sie die meiste Zeit nicht, aber sie sind schön für diese seltsamen Fälle.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.