goto verwenden oder nicht?

Lesezeit: 8 Minuten

Benutzer-Avatar
Abhinav Upadhyay

Diese Frage mag klischeehaft klingen, aber ich bin hier in einer Situation.

Ich versuche, einen Finite-State-Automaten zu implementieren, um eine bestimmte Zeichenfolge in C zu analysieren. Als ich anfing, den Code zu schreiben, wurde mir klar, dass der Code möglicherweise besser lesbar ist, wenn ich Labels verwende, um die verschiedenen Zustände zu markieren, und goto verwende, um von einem Zustand zu springen ein anderes, wie der Fall kommt.

Die Verwendung der Standard-Breaks und Flag-Variablen ist in diesem Fall ziemlich umständlich und es ist schwierig, den Status zu verfolgen.

Welche Vorgehensweise ist besser? Mehr als alles andere mache ich mir Sorgen, dass es einen schlechten Eindruck bei meinem Chef hinterlassen könnte, da ich ein Praktikum mache.

  • Unbedingt verlinken: xkcd.com/292

    – Kimmo Puputti

    23. Juni 2010 um 21:43 Uhr

  • Ich bin neugierig. Können Sie einen Before-Goto/After-Goto-Snapshot des Codes posten?

    Aryabhatta

    23. Juni 2010 um 21:43 Uhr

  • Wir kennen Ihren Chef nicht und wissen nicht, welche Vorurteile er hat. Wenn Sie also clever aussehen möchten, verwenden Sie Funktionszeiger (siehe meine Antwort auf eine andere Frage hier: stackoverflow.com/questions/1371460/state-machines-tutorials/…)

    – qrdl

    23. Juni 2010 um 22:11 Uhr

  • Endliche Zustandsautomaten sind die vorletzte Antithese dessen, was die Informatik seit ihren Anfängen zu erreichen versucht. Es muss ein Dutzend bessere Möglichkeiten geben, einen String zu parsen. Versuchen Sie ehrlich, einen zu finden.

    – Amardeep AC9MF

    23. Juni 2010 um 22:47 Uhr

  • @Kimmo Puputti- Meinst du nicht “goto xkcd.com/292 ” ?

    – bta

    24. Juni 2010 um 17:49 Uhr


Daran ist grundsätzlich nichts auszusetzen goto. Der Grund, warum sie oft als “Tabu” betrachtet werden, liegt in der Art und Weise, wie einige Programmierer (oft aus der Assembler-Welt kommend) sie verwenden, um “Spaghetti”-Code zu erstellen, der fast unmöglich zu verstehen ist. Wenn Sie verwenden können goto Anweisungen, während Sie Ihren Code sauber, lesbar und fehlerfrei halten, dann haben Sie mehr Leistung.

Verwenden goto Anweisungen und ein Codeabschnitt für jeden Zustand ist definitiv eine Möglichkeit, eine Zustandsmaschine zu schreiben. Die andere Methode besteht darin, eine Variable zu erstellen, die den aktuellen Status enthält, und eine switch-Anweisung (oder ähnliches) zu verwenden, um auszuwählen, welcher Codeblock basierend auf dem Wert der Statusvariablen ausgeführt werden soll. Siehe Aidan Cullys Antwort für eine gute Vorlage mit dieser zweiten Methode.

In Wirklichkeit sind sich die beiden Methoden sehr ähnlich. Wenn Sie einen Zustandsautomaten mit der State-Variable-Methode schreiben und kompilieren, kann die generierte Assembly Code sehr gut ähneln, der mit dem geschrieben wurde goto -Methode (abhängig vom Optimierungsgrad Ihres Compilers). Das goto -Methode kann als Optimieren der zusätzlichen Variable und Schleife aus der Zustandsvariablen-Methode angesehen werden. Welche Methode Sie verwenden, ist eine Frage der persönlichen Wahl, und solange Sie funktionierenden, lesbaren Code produzieren, würde ich hoffen, dass Ihr Chef nicht anders über Sie denkt, wenn Sie eine Methode gegenüber der anderen verwenden.

Wenn Sie diesen Code zu einer vorhandenen Codebasis hinzufügen, die bereits Zustandsmaschinen enthält, würde ich empfehlen, dass Sie der bereits verwendeten Konvention folgen.

  • Es ist klug, der bereits verwendeten Konvention zu folgen

    – Cervo

    23. Juni 2010 um 22:50 Uhr

  • Vielen Dank. Das ist ein ziemlich nützlicher Rat. Dieses Zustandsmaschinenproblem ist gerade in meinem eigenen Projekt aufgetreten, und der von anderen bereitgestellte Code ist wirklich sauber, also werde ich diesen Ansatz verfolgen.

    – Abhinav Upadhyay

    24. Juni 2010 um 5:39 Uhr

Verwendung einer goto für die Implementierung einer Zustandsmaschine ist oft sinnvoll. Wenn Sie wirklich Bedenken haben, ein goto zu verwenden, ist es oft eine vernünftige Alternative, ein zu haben state Variable, die Sie ändern, und a switch darauf basierende aussage:

typedef enum {s0,s1,s2,s3,s4,...,sn,sexit} state;

state nextstate;
int done = 0;

nextstate = s0;  /* set up to start with the first state */
while(!done)
   switch(nextstate)
      {
         case s0:
            nextstate = do_state_0();
            break;
         case s1:
            nextstate = do_state_1();
            break;
         case s2:
            nextstate = do_state_2();
            break;
         case s3:
             .
             .
             .
             .
         case sn:
            nextstate = do_state_n();
            break;
         case sexit:
            done = TRUE;
            break;
         default:
            /*  some sort of unknown state */
            break;
      }

Benutzer-Avatar
Vinko Vrsalović

Ich würde einen FSM-Generator verwenden, wie Ragelwenn ich bei meinem Chef einen guten Eindruck hinterlassen wollte.

Der Hauptvorteil dieses Ansatzes besteht darin, dass Sie Ihren Zustandsautomaten auf einer höheren Abstraktionsebene beschreiben können und sich keine Gedanken darüber machen müssen, ob Sie goto oder einen Schalter verwenden. Ganz zu schweigen von dem besonderen Fall von Ragel, dass Sie automatisch schöne Diagramme Ihres FSM erhalten, Aktionen an jeder Stelle einfügen, die Anzahl der Zustände automatisch minimieren und verschiedene andere Vorteile haben können. Habe ich erwähnt, dass die generierten FSMs auch sehr schnell sind?

Die Nachteile sind, dass sie schwieriger zu debuggen sind (automatische Visualisierung hilft hier sehr) und dass Sie ein neues Tool lernen müssen (was sich wahrscheinlich nicht lohnt, wenn Sie eine einfache Maschine haben und wahrscheinlich nicht häufig Maschinen schreiben. )

  • Die Probleme, die ich bei der Verwendung von nicht trivialen Codegeneratoren hatte, sind 1) das Debuggen und 2) die Notwendigkeit, ein neues Tool zu lernen … Wenn das Tool mir etwas Nützliches gibt, das ohne es schwer zu erreichen wäre, oder wenn Ich werde das Tool ausreichend nutzen, um den Zeitaufwand für das Erlernen auszugleichen (beachten Sie hier die Debuggbarkeit als wichtigen Faktor), großartig, ich werde es tun. Ich würde mich nicht um ein triviales FSM kümmern, aber vielleicht um ein komplizierteres.

    – Aidan Cully

    23. Juni 2010 um 22:37 Uhr

  • Ich würde GOTO nicht befürworten, weil die FSM-Generatoren es tun. Es gibt einen Unterschied zwischen einem (vermutlich getesteten) Codegenerator, der GOTOs erstellt, und einem Programmierer, der dies tut. Ich bin mir nicht sicher, welchen Weg ich hier gehen soll. Aber ich würde die Entscheidung nicht durch die Tatsache beeinflussen lassen, dass die Generatoren GOTOs verwenden (außer natürlich, wenn ich einen Generator dafür schreibe …).

    – Cervo

    23. Juni 2010 um 22:47 Uhr

  • @Aidan: Ich stimme größtenteils zu. Obwohl einige Generatoren Debugging-Möglichkeiten haben, die das Debugging-Problem lindern, mindern sie nicht das Problem „ein neues Tool lernen“. Für einfache Maschinen sind sie wahrscheinlich übertrieben, es sei denn, Sie kennen das Tool bereits, was ein Anreiz sein könnte, es trotzdem zu lernen 🙂

    – Vinko Vrsalović

    23. Juni 2010 um 22:48 Uhr


  • @Cervo: Richtig. Meine Absicht war nicht, die Verwendung von goto für benutzergenerierten Code zu befürworten, obwohl es beim erneuten Lesen des Absatzes so erscheint.

    – Vinko Vrsalović

    23. Juni 2010 um 22:51 Uhr

  • @Aidan, @Cervo: Bearbeitet, um Ihre Kommentare widerzuspiegeln.

    – Vinko Vrsalović

    23. Juni 2010 um 22:57 Uhr


Ich würde eine Variable verwenden, die nachverfolgt, in welchem ​​​​Zustand Sie sich befinden, und einen Schalter, um sie zu handhaben:

fsm_ctx_t ctx = ...;
state_t state = INITIAL_STATE;

while (state != DONE)
{
    switch (state)
    {
    case INITIAL_STATE:
    case SOME_STATE:
        state = handle_some_state(ctx)
        break;

    case OTHER_STATE:
        state = handle_other_state(ctx);
        break;
    }
}

Goto ist kein notwendiges Übel, und ich muss Denis stark widersprechen, ja, goto ist in den meisten Fällen eine schlechte Idee, aber es gibt Verwendungsmöglichkeiten. Die größte Angst bei goto ist der sogenannte “Spagetti-Code”, nicht nachvollziehbare Codepfade. Wenn man das vermeiden kann und immer klar ist, wie sich der Code verhält und man nicht mit einem goto aus der Funktion springt, spricht nichts gegen goto. Verwenden Sie es einfach mit Vorsicht, und wenn Sie versucht sind, es zu verwenden, bewerten Sie die Situation wirklich und finden Sie eine bessere Lösung. Wenn dies nicht möglich ist, kann goto verwendet werden.

Benutzer-Avatar
Wallyk

Vermeiden goto es sei denn, die hinzugefügte Komplexität (zu vermeiden) ist verwirrender.

Bei praktischen technischen Problemen wird der Platz für goto sehr sparsam verwendet. Akademiker und Nicht-Ingenieure ringen unnötigerweise die Finger über die Verwendung goto. Das heißt, wenn Sie sich in eine Implementierungsecke malen, in der viele goto ist der einzige Ausweg, überdenken Sie die Lösung.

Eine korrekt funktionierende Lösung ist in der Regel das primäre Ziel. Richtig machen und wartbar (durch Minimierung der Komplexität) hat viele Lebenszyklusvorteile. Lassen Sie es zuerst funktionieren und reinigen Sie es dann nach und nach, vorzugsweise durch Vereinfachen und Entfernen von Hässlichkeit.

Ich kenne Ihren spezifischen Code nicht, aber gibt es einen Grund wie diesen:

typedef enum {
    STATE1, STATE2, STATE3
} myState_e;

void myFsm(void)
{
    myState_e State = STATE1;

    while(1)
    {
        switch(State)
        {
            case STATE1:
                State = STATE2;
                break;
            case STATE2:
                State = STATE3;
                break;
            case STATE3:
                State = STATE1;
                break;
        }
    }
}

würde nicht für dich funktionieren? Es nutzt nicht gotound ist relativ einfach zu folgen.

Bearbeiten: Alle diese State = Fragmente verletzen DRY, also könnte ich stattdessen so etwas tun:

typedef int (*myStateFn_t)(int OldState);

int myStateFn_Reset(int OldState, void *ObjP);
int myStateFn_Start(int OldState, void *ObjP);
int myStateFn_Process(int OldState, void *ObjP);

myStateFn_t myStateFns[] = {
#define MY_STATE_RESET 0
   myStateFn_Reset,
#define MY_STATE_START 1
   myStateFn_Start,
#define MY_STATE_PROCESS 2
   myStateFn_Process
}

int myStateFn_Reset(int OldState, void *ObjP)
{
    return shouldStart(ObjP) ? MY_STATE_START : MY_STATE_RESET;
}

int myStateFn_Start(int OldState, void *ObjP)
{
    resetState(ObjP);
    return MY_STATE_PROCESS;
}

int myStateFn_Process(int OldState, void *ObjP)
{
    return (process(ObjP) == DONE) ? MY_STATE_RESET : MY_STATE_PROCESS;
}

int stateValid(int StateFnSize, int State)
{
    return (State >= 0 && State < StateFnSize);
}

int stateFnRunOne(myStateFn_t StateFns, int StateFnSize, int State, void *ObjP)
{
    return StateFns[OldState])(State, ObjP);
}

void stateFnRun(myStateFn_t StateFns, int StateFnSize, int CurState, void *ObjP)
{
    int NextState;

    while(stateValid(CurState))
    {
        NextState = stateFnRunOne(StateFns, StateFnSize, CurState, ObjP);
        if(! stateValid(NextState))
            LOG_THIS(CurState, NextState);
        CurState = NextState;
    }
}

das ist natürlich viel länger als der erste Versuch (komische Sache mit DRY). Aber es ist auch robuster – wenn der Zustand nicht von einer der Zustandsfunktionen zurückgegeben wird, führt dies zu einer Compiler-Warnung, anstatt ein Fehlen stillschweigend zu ignorieren State = im früheren Code.

  • …ist das nicht einfach goto in Kraft?

    – Amara

    23. Juni 2010 um 21:53 Uhr

  • Nein, denn der Compiler kümmert sich um die Labels, das Springen und den Stack. Es ist, als würde man vorschlagen, dass eine Million Zeilen einer ausdrucksstarken Sprache wie Lisp oder Python Assembler sein könnten. Sicher, sie könnten dieselbe Aufgabe erledigen, nachdem alles gesagt und getan ist, aber sie sind nicht im Entferntesten gleich.

    – Welpe

    23. Juni 2010 um 22:04 Uhr

1290430cookie-checkgoto verwenden oder nicht?

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

Privacy policy