Ich frage mich nur, ob jemand gute Tutorials im Internet zur Entwicklung von Zustandsautomaten kennt. Oder E-Books?
Ich fange an, an Zustandsautomaten zu arbeiten und brauche nur etwas Allgemeines, um loszulegen.
Ich frage mich nur, ob jemand gute Tutorials im Internet zur Entwicklung von Zustandsautomaten kennt. Oder E-Books?
Ich fange an, an Zustandsautomaten zu arbeiten und brauche nur etwas Allgemeines, um loszulegen.
Antworten:
Zustandsautomaten sind in C sehr einfach, wenn Sie Funktionszeiger verwenden.
Grundsätzlich benötigen Sie zwei Arrays - eines für Statusfunktionszeiger und eines für Statusübergangsregeln. Jede Statusfunktion gibt den Code zurück. Sie suchen die Statusübergangstabelle nach Status und Rückkehrcode, um den nächsten Status zu finden, und führen ihn dann einfach aus.
int entry_state(void);
int foo_state(void);
int bar_state(void);
int exit_state(void);
/* array and enum below must be in sync! */
int (* state[])(void) = { entry_state, foo_state, bar_state, exit_state};
enum state_codes { entry, foo, bar, end};
enum ret_codes { ok, fail, repeat};
struct transition {
enum state_codes src_state;
enum ret_codes ret_code;
enum state_codes dst_state;
};
/* transitions from end state aren't needed */
struct transition state_transitions[] = {
{entry, ok, foo},
{entry, fail, end},
{foo, ok, bar},
{foo, fail, end},
{foo, repeat, foo},
{bar, ok, end},
{bar, fail, end},
{bar, repeat, foo}};
#define EXIT_STATE end
#define ENTRY_STATE entry
int main(int argc, char *argv[]) {
enum state_codes cur_state = ENTRY_STATE;
enum ret_codes rc;
int (* state_fun)(void);
for (;;) {
state_fun = state[cur_state];
rc = state_fun();
if (EXIT_STATE == cur_state)
break;
cur_state = lookup_transitions(cur_state, rc);
}
return EXIT_SUCCESS;
}
Ich stelle keine lookup_transitions()
Funktion ein, da sie trivial ist.
So mache ich seit Jahren Zustandsmaschinen.
int state_transitions[][3] = { [entry] = { foo, end, foo }, ... } /* ok, fail, repeat */
src_state
und dst_state
als Funktionszeiger zu haben? Ich verstehe den Vorteil nicht, sie vom Typ enum zu haben, wenn Sie sie nur verwenden, um einige Funktionszeiger in einem Array nachzuschlagen.
Ich bevorzuge die Verwendung von Funktionszeigern gegenüber gigantischen switch
Anweisungen, aber im Gegensatz zu qrdls Antwort verwende ich normalerweise keine expliziten Rückkehrcodes oder Übergangstabellen.
In den meisten Fällen möchten Sie außerdem, dass ein Mechanismus zusätzliche Daten weitergibt. Hier ist ein Beispiel für eine Zustandsmaschine:
#include <stdio.h>
struct state;
typedef void state_fn(struct state *);
struct state
{
state_fn * next;
int i; // data
};
state_fn foo, bar;
void foo(struct state * state)
{
printf("%s %i\n", __func__, ++state->i);
state->next = bar;
}
void bar(struct state * state)
{
printf("%s %i\n", __func__, ++state->i);
state->next = state->i < 10 ? foo : 0;
}
int main(void)
{
struct state state = { foo, 0 };
while(state.next) state.next(&state);
}
main
keinen Wert zurück. . . sollte es?
main()
impliziten Rückkehr0
while
Bedingung in der letzten Zeile? Rufen Sie an foo()
? Welche Standardargumente werden angenommen, wenn keine angegeben sind?
state.next
(einen Funktionszeiger) auf die Adresse von foo
. Also state.next(&state)
ist es dasselbe wie foo(&state)
(die erste Iteration der Schleife zeigt später woanders hin). Zum Vergleich: Wenn dies C ++ foo()
wäre , wäre es ein Mitglied der State
Klasse ( State::foo()
) und würde keine Parameter annehmen. Sein Körper würde this->next = bar
anstelle von verwenden state->next = bar
. In C müssen Sie das Äquivalent des this
Zeigers explizit übergeben, da keine zustandsbehafteten Klassenbereiche vorhanden sind.
Leider sind die meisten Artikel über Zustandsautomaten für C ++ oder andere Sprachen geschrieben, die Polymorphismus direkt unterstützen, da es hilfreich ist, die Zustände in einer FSM-Implementierung als Klassen zu modellieren, die von einer abstrakten Zustandsklasse abgeleitet sind.
Es ist jedoch ziemlich einfach, Zustandsautomaten in C zu implementieren, indem entweder switch-Anweisungen verwendet werden, um Ereignisse in Zustände zu versenden (bei einfachen FSMs codieren sie so ziemlich direkt) oder Tabellen, um Ereignisse Zustandsübergängen zuzuordnen.
Hier finden Sie einige einfache, aber anständige Artikel zu einem Grundgerüst für Zustandsautomaten in C:
Bearbeiten : Site "in Wartung", Webarchiv-Links:
switch
Anweisungsbasierte Zustandsautomaten verwenden häufig eine Reihe von Makros, um die Mechanik der switch
Anweisung zu "verbergen" (oder verwenden eine Reihe von if
/ then
/ else
Anweisungen anstelle von a switch
) und erstellen eine "FSM-Sprache" zur Beschreibung der Zustandsmaschine in C. Quelle. Ich persönlich bevorzuge den tabellenbasierten Ansatz, aber diese haben sicherlich Vorteile, sind weit verbreitet und können insbesondere für einfachere FSMs effektiv sein.
Ein solches Framework wird von Steve Rabin in "Game Programming Gems", Kapitel 3.0 (Entwerfen einer allgemeinen robusten KI-Engine) beschrieben .
Ein ähnlicher Satz von Makros wird hier diskutiert:
Wenn Sie sich auch für C ++ - Zustandsmaschinenimplementierungen interessieren, finden Sie noch viel mehr. Ich werde Hinweise posten, wenn Sie interessiert sind.
Zustandsautomaten sind nicht von Natur aus ein Tutorial, das erklärt oder sogar verwendet werden muss. Ich schlage vor, dass Sie sich die Daten ansehen und wie sie analysiert werden müssen.
Zum Beispiel musste ich das Datenprotokoll für einen Near Space-Ballonflugcomputer analysieren. Es speicherte Daten auf der SD-Karte in einem bestimmten Format (binär), das in eine durch Kommas getrennte Datei analysiert werden musste. Die Verwendung einer Zustandsmaschine ist hierfür am sinnvollsten, da wir je nach der nächsten Information ändern müssen, was wir analysieren.
Der Code wird mit C ++ geschrieben und ist als ParseFCU verfügbar . Wie Sie sehen können, erkennt es zuerst, welche Version wir analysieren, und von dort aus werden zwei verschiedene Zustandsautomaten eingegeben.
Es tritt in einem bekanntermaßen guten Zustand in die Zustandsmaschine ein. An diesem Punkt beginnen wir mit dem Parsen. Je nachdem, auf welche Zeichen wir stoßen, wechseln wir entweder zum nächsten Zustand oder kehren zu einem vorherigen Zustand zurück. Dies ermöglicht es dem Code grundsätzlich, sich selbst an die Art und Weise anzupassen, in der die Daten gespeichert werden, und ob bestimmte Daten überhaupt vorhanden sind oder nicht.
In meinem Beispiel ist die GPS-Zeichenfolge keine Voraussetzung für die Protokollierung des Flugcomputers. Daher kann die Verarbeitung der GPS-Zeichenfolge übersprungen werden, wenn die Endbytes für diesen einzelnen Protokollschreibvorgang gefunden werden.
Zustandsautomaten sind einfach zu schreiben, und im Allgemeinen folge ich der Regel, dass sie fließen sollen. Eingaben, die durch das System gehen, sollten mit gewisser Leichtigkeit von Zustand zu Zustand fließen.
Das ist alles was Sie wissen müssen.
int state = 0;
while (state < 3)
{
switch (state)
{
case 0:
// Do State 0 Stuff
if (should_go_to_next_state)
{
state++;
}
break;
case 1:
// Do State 1 Stuff
if (should_go_back)
{
state--;
}
else if (should_go_to_next_state)
{
state++;
}
break;
case 2:
// Do State 2 Stuff
if (should_go_back_two)
{
state -= 2;
}
else if (should_go_to_next_state)
{
state++;
}
break;
default:
break;
}
}
Die objektorientierte Modellierung in Echtzeit war fantastisch (1994 veröffentlicht und jetzt für nur 81 Cent plus 3,99 US-Dollar Versand verkauft).
In C gibt es eine Menge Lektionen zum Erlernen der handwerklichen Zustandsmaschinen, aber ich möchte auch den Ragel-Zustandsmaschinen-Compiler vorschlagen:
http://www.complang.org/ragel/
Es gibt eine recht einfache Möglichkeit, Zustandsautomaten zu definieren. Anschließend können Sie Diagramme erstellen, Code in verschiedenen Stilen (tabellengesteuert, goto-gesteuert) generieren, diesen Code analysieren, wenn Sie möchten usw. Und es ist leistungsstark und kann in der Produktion verwendet werden Code für verschiedene Protokolle.
Zustandsautomaten können für ein komplexes Problem sehr komplex sein. Sie sind auch unerwarteten Fehlern ausgesetzt. Sie können sich in einen Albtraum verwandeln, wenn jemand auf einen Fehler stößt oder die Logik in Zukunft ändern muss. Sie sind auch ohne das Zustandsdiagramm schwer zu verfolgen und zu debuggen. Strukturierte Programmierung ist viel besser (zum Beispiel würden Sie wahrscheinlich keine Zustandsmaschine auf Mainline-Ebene verwenden). Sie können die strukturierte Programmierung auch im Interrupt-Kontext verwenden (wo normalerweise Zustandsautomaten verwendet werden). Weitere Informationen finden Sie in diesem Artikel "Makros zum Simulieren von Multitasking- / Blockierungscode auf Interrupt-Ebene" unter codeproject.com.