Warum verwenden einige Kernel-Programmierer goto statt einfacher While-Schleifen?

Lesezeit: 7 Minuten

Benutzeravatar von musicmatze
musikmatze

Als ich C lernte, sagte mir der Lehrer den ganzen Tag: “Benutze nicht goto, das ist eine schlechte Angewohnheit, es ist hässlich, es ist gefährlich!” usw.

Warum verwenden dann einige Kernel-Programmierer gotozum Beispiel in dieser Funktionwo es durch ein einfaches ersetzt werden könnte

while(condition) {} 

oder

do {} while(condition);

Ich kann das nicht verstehen. Ist es in manchen Fällen besser, statt goto zu verwenden? while/dowhile? Und wenn ja, warum?

  • Interessante Frage. Als Nebenbemerkung würde ich nicht kritisieren goto so viel. es ist normalerweise ein schlechter Geruch und so, aber manchmal lohnt es sich, den Code einfacher zu halten – oft zur Bereinigung und Behandlung von Fehlersituationen in komplizierten Funktionen. Seien Sie nicht religiös wegen unserer Kleinen goto Bruder, es hat seinen Nutzen 🙂

    – Piotr Zierhoffer

    21. Oktober 2012 um 19:06 Uhr


  • Nun ja, das sagen sie normalerweise 🙂 Und es ist oft wahr. Für Unterrichtszwecke kann “oft” leicht mit “immer” übersetzt werden, um die Dinge einfacher zu halten

    – Piotr Zierhoffer

    21. Oktober 2012 um 19:15 Uhr

  • Bitten Sie ihn, die Aktion von a zu beschreiben break; Anweisung in einer strukturierten Schleife (wie while, for usw.), ohne die Wörter zu verwenden goto ,leaveoder jump (das asm-Äquivalent von goto). Er möchte, dass Sie davon ausgehen, dass Sie es verwenden goto ist ein Zeichen für nachträgliches Patchen schlecht durchdachter Codelogik. In vielen/meisten Fällen hat er wahrscheinlich Recht, aber das Schlüsselwort dort ist “wahrscheinlich”. Es ist NICHT immer der Fall. Während Sie die Sprache lernen, ist es am besten, sie aus Gründen der Vernunft zu vermeiden, aber erschießen Sie nicht den Typen, der wirklich weiß, was er damit macht.

    – WhozCraig

    21. Oktober 2012 um 19:18 Uhr


  • xkcd.com/292

    – DaveShaw

    21. Oktober 2012 um 23:04 Uhr

  • Hier ist eine E-Mail von Robert Love über einen der Gründe goto wird im Linux-Kernel verwendet.

    – Iskata

    21. Oktober 2012 um 23:33 Uhr

Historischer Zusammenhang: Wir sollten uns daran erinnern, dass Dijkstra schrieb Gehe zu Wird als schädlich angesehen im Jahr 1968, als viele Programmierer benutzten goto als Ersatz für strukturierte Programmierung (if, while, foretc.).

Es ist 44 Jahre später und es ist selten, diese Verwendung von zu finden goto in der Wildnis. Die strukturierte Programmierung hat schon längst gewonnen.

Fall Analyse:

Der Beispielcode sieht so aus:

    SETUP...
again:
    COMPUTE SOME VALUES...
    if (cmpxchg64(ptr, old_val, val) != old_val)
        goto again;

Die strukturierte Version sieht so aus:

SETUP...
do {
    COMPUTE SOME VALUES...
} while (cmpxchg64(ptr, old_val, val) != old_val);

Wenn ich mir die strukturierte Version anschaue, denke ich sofort: „Das ist eine Schleife“. Wenn ich mir die ansehe goto Version, ich stelle es mir als eine gerade Linie mit einem “versuchen Sie es noch einmal”-Fall am Ende vor.

Das goto Version hat beides SETUP und COMPUTE SOME VALUES in derselben Spalte, was betont, dass der Kontrollfluss die meiste Zeit durch beide fließt. Die strukturierte Version setzt SETUP und COMPUTE SOME VALUES auf verschiedenen Spalten, was betont, dass die Kontrolle unterschiedlich durch sie hindurchgehen kann.

Die Frage hier ist, welche Art von Betonung möchten Sie in den Code legen? Damit kann man vergleichen goto zur Fehlerbehandlung:

Strukturierte Version:

if (do_something() != ERR) {
    if (do_something2() != ERR) {
        if (do_something3() != ERR) {
            if (do_something4() != ERR) {
                ...

Goto-Version:

if (do_something() == ERR)  // Straight line
    goto error;             // |
if (do_something2() == ERR) // |
    goto error;             // |
if (do_something3() == ERR) // |
    goto error;             // V
if (do_something4() == ERR) // emphasizes normal control flow
    goto error;

Der generierte Code ist im Grunde derselbe, sodass wir ihn uns als typografisches Problem vorstellen können, wie z. B. Einrückungen.

  • Kernel-Programmierer stimmen zu. Wenn Sie ERR1, ERR2 … ERRN hätten, dann könnten Sie goto clean_err1;/etc und kehren Sie die Änderungen um, als ob es sich um einen Stack handelte.

    – Iskata

    21. Oktober 2012 um 23:35 Uhr


  • +1, um zu zeigen, wie goto den erwarteten Kontrollfluss betonen kann.

    – Löwe

    22. Oktober 2012 um 6:35 Uhr

  • Wenn Code mitten in einer inneren Schleife entdeckt, dass eine äußere Schleife beendet oder neu gestartet werden muss, oder wenn er mitten in einer switch Anweisung, dass eine umschließende Schleife beendet werden muss, wie sollte das geschrieben werden, ohne zu verwenden goto oder das Hinzufügen von irrelevanten Flags (die noch schlimmer sind)?

    – Superkatze

    24. Oktober 2016 um 21:52 Uhr

  • @supercat: Das ist eine Frage des Programmierstils, die schwer zu beantworten ist, ohne Einzelheiten zu sehen. Es ist sicherlich nicht einfacher, diese Frage zu beantworten als eine Frage wie “Wie soll ich eine Schleife verlassen” – nun, Sie können abbrechen, die Bedingung verwenden, ein Flag verwenden, ein goto verwenden, ein Return verwenden usw einfach zu viele Auswahlmöglichkeiten, keine endgültige Antwort möglich.

    – Dietrich Ep

    24. Oktober 2016 um 22:12 Uhr

Bei diesem Beispiel vermute ich, dass es darum ging, die SMP-Unterstützung in Code nachzurüsten, der ursprünglich nicht SMP-sicher geschrieben wurde. Hinzufügen eines goto again; Pfad ist viel einfacher und weniger invasiv als die Umstrukturierung der Funktion.

Ich kann nicht sagen, dass ich diesen Stil sehr mag, aber ich denke auch, dass es falsch ist, ihn zu vermeiden goto aus ideologischen Gründen. Ein Sonderfall von goto Verwendung (anders als in diesem Beispiel) ist wo goto wird nur verwendet, um sich in einer Funktion vorwärts zu bewegen, niemals rückwärts. Diese Klasse von Verwendungen führt niemals zu Schleifenkonstrukten, die daraus entstehen gotound es ist fast immer der einfachste und klarste Weg, das erforderliche Verhalten zu implementieren (was normalerweise das Aufräumen und Zurückgeben bei Fehlern ist).

  • +1 für Aussage über goto 😉 Und der Grund scheint auch ganz logisch zu sein. Andererseits ist das nicht möglich goto könnte aus der Sicht eines Verzweigungsprädiktors ein anderes Ergebnis erzeugen?

    – Piotr Zierhoffer

    21. Oktober 2012 um 19:09 Uhr

  • IIRC die again: loop-Methode stammt aus dem BSD-Netzwerkcode. Und es ist sehr sauber, da der typische Codepfad keinen (bedingten) Sprung enthält. Der außergewöhnliche Weg tut es.

    – Wildpässer

    21. Oktober 2012 um 19:09 Uhr

  • Idealerweise sollte ein Compiler eine mit geschriebene Schleife behandeln goto identisch mit einem mit Schleifenkonstrukten geschriebenen, dh beide sollten zunächst als bedingte Verzweigungen dargestellt werden (if ... goto ...;) und dann sollte der Optimierer sein Ding machen. Ich bin mir nicht sicher, inwieweit dies bei heutigen Compilern zutrifft, aber es ist eine wichtige Eigenschaft, um zu vermeiden, dass Programmierstile gefördert werden, bei denen der Autor eine Schleifendarstellung wählt, auf deren Grundlage man den besten Code generiert, und nicht auf welcher ist am logischsten/selbstdokumentierend.

    – R.. GitHub HÖR AUF, EIS ZU HELFEN

    21. Oktober 2012 um 19:12 Uhr

  • Bei aktuellen Compilern ist dies normalerweise der Fall; Sie optimieren den Codeflussgraphen nach dem while wurde in einen Konditional dekonstruiert goto im Inneren.

    – Donal Fellows

    22. Oktober 2012 um 9:28 Uhr

Sehr gute Frage, und ich denke, nur der Autor kann eine endgültige Antwort geben. Ich füge meine Spekulation hinzu, indem ich sage, dass es damit hätte beginnen können, es für die Fehlerbehandlung zu verwenden, wie von @Izkata erklärt, und die Tore waren dann offen, um es auch für grundlegende Schleifen zu verwenden.

Die Verwendung der Fehlerbehandlung ist meiner Meinung nach in der Systemprogrammierung legitim. Eine Funktion weist im Laufe ihres Fortschritts nach und nach Speicher zu, und wenn ein Fehler auftritt, wird dies der Fall sein goto das entsprechende Etikett, um die Ressourcen ab diesem Punkt in umgekehrter Reihenfolge freizugeben.

Wenn also der Fehler nach der ersten Zuordnung auftritt, wird zum letzten Fehlerlabel gesprungen, um nur eine Ressource freizugeben. Wenn der Fehler nach der letzten Zuweisung auftritt, springt er ebenso zum ersten Fehlerlabel und wird von dort aus ausgeführt, wodurch alle Ressourcen bis zum Ende der Funktion freigegeben werden. Ein solches Muster für die Fehlerbehandlung muss dennoch sorgfältig verwendet werden, insbesondere wenn Code geändert wird, Valgrind und Komponententests werden dringend empfohlen. Aber es ist wohl lesbarer und wartbarer als die alternativen Ansätze.

Eine goldene Nutzungsregel goto ist es, den sogenannten Spaghetti-Code zu vermeiden. Versuchen Sie, Linien zwischen den einzelnen zu ziehen goto Anweisung und das entsprechende Etikett. Wenn Sie Linien haben, die sich kreuzen, nun, Sie haben eine Linie überschritten :). Eine solche Verwendung von goto ist sehr schwer zu lesen und eine häufige Quelle für schwer zu verfolgende Fehler, da sie in Sprachen wie BASIC gefunden werden, die sich darauf verlassen, um den Fluss zu kontrollieren.

Wenn Sie nur eine einfache Schleife machen, werden sich keine Linien kreuzen, so dass es immer noch lesbar und wartbar ist und weitgehend eine Frage des Stils wird. Da sie jedoch genauso einfach mit den von der Sprache bereitgestellten Schleifenschlüsselwörtern ausgeführt werden können, wie Sie in Ihrer Frage angegeben haben, wäre meine Empfehlung immer noch, die Verwendung zu vermeiden goto for-Schleifen, einfach weil die for, do/while oder while Konstrukte sind von Natur aus eleganter.

1411910cookie-checkWarum verwenden einige Kernel-Programmierer goto statt einfacher While-Schleifen?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy