GNU/Linux >> LINUX-Kenntnisse >  >> Linux

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

Historischer Kontext: Wir sollten uns daran erinnern, dass Dijkstra Goto Considered Harmful geschrieben hat 1968, als viele Programmierer goto verwendeten als Ersatz für die strukturierte Programmierung (if , while , for , etc.).

44 Jahre später ist diese Verwendung von goto selten zu finden in der Wildnis. Strukturierte Programmierung hat schon längst gewonnen.

Fallanalyse:

Der Beispielcode sieht folgendermaßen 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 goto ansehe Version, ich stelle es mir als eine gerade Linie mit einem „Versuch es nochmal“-Fall am Ende vor.

Die goto Version hat sowohl 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? Sie können dies mit goto vergleichen 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, also können wir ihn uns als typografisches Anliegen vorstellen, wie Einrückungen.


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, 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 der Zeit nach und nach Speicher zu, und wenn ein Fehler auftritt, wird goto ausgegeben das entsprechende Label, 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 besser lesbar und wartbarer als die alternativen Ansätze.

Eine goldene Regel zur Verwendung von goto ist es, den sogenannten Spaghetti-Code zu vermeiden. Versuchen Sie, Linien zwischen jedem goto zu ziehen 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, wie sie in Sprachen wie BASIC zu finden sind, die sich darauf zur Flusskontrolle verlassen.

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 von goto zu vermeiden for-Schleifen, einfach weil die for , do/while oder while Konstrukte sind von Natur aus eleganter.


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 fehl am Platz ist, goto zu vermeiden aus ideologischen Gründen. Ein Sonderfall von goto Verwendung (abweichend von diesem Beispiel) ist wobei goto wird nur verwendet, um sich in einer Funktion vorwärts zu bewegen, niemals rückwärts. Diese Verwendungsklasse führt niemals zu Schleifenkonstrukten, die aus goto entstehen , und 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).


Linux
  1. Warum Programmierer die Linux-Paketierung lieben

  2. Linux – Warum verwenden wir Su – und nicht nur Su?

  3. Warum verursacht fclose(NULL) von glibc einen Segmentierungsfehler, anstatt einen Fehler zurückzugeben?

  4. Verwenden Sie sed mit ignorierter Groß-/Kleinschreibung, während Sie Text vor einem Muster hinzufügen

  5. Warum wird in der Kernel-Programmierung u8 u16 u32 u64 anstelle von unsigned int verwendet?

Warum ich unter Linux exa anstelle von ls verwende

Warum ich rxvt als Terminal verwende

So verwenden Sie Schleifen im Ansible Playbook

Die 10 wichtigsten Gründe für die Verwendung von Linux

Warum Nerds Linux verwenden

Linux vs. Mac OS:15 Gründe, warum Sie Linux anstelle von Mac OS verwenden sollten