Ich bastle ein kleines Projekt in gemischtem C und C ++. Ich baue eine kleine Zustandsmaschine im Herzen eines meiner Arbeitsthreads.
Ich habe mich gefragt, ob Sie Gurus auf SO Ihre State-Machine-Design-Techniken teilen würden.
HINWEIS: Ich bin in erster Linie nach bewährten Implementierungstechniken.
AKTUALISIERT: Aufgrund all der großartigen Beiträge, die auf SO gesammelt wurden, habe ich mich für diese Architektur entschieden:
c++
c
architecture
state-machine
jldupont
quelle
quelle
Antworten:
Zustandsmaschinen, die ich zuvor entworfen habe (C, nicht C ++), sind alle auf ein
struct
Array und eine Schleife zurückzuführen. Die Struktur besteht im Wesentlichen aus einem Status und einem Ereignis (zum Nachschlagen) und einer Funktion, die den neuen Status zurückgibt, etwa:Dann definieren Sie Ihre Zustände und Ereignisse mit einfachen Definitionen (diese
ANY
sind spezielle Markierungen, siehe unten):Dann definieren Sie alle Funktionen, die von den Übergängen aufgerufen werden:
Alle diese Funktionen sind so geschrieben, dass sie keine Variablen annehmen und den neuen Status für die Zustandsmaschine zurückgeben. In diesem Beispiel werden globale Variablen verwendet, um bei Bedarf Informationen an die Statusfunktionen zu übergeben.
Die Verwendung von Globals ist nicht so schlecht, wie es sich anhört, da der FSM normalerweise in einer einzelnen Kompilierungseinheit eingeschlossen ist und alle Variablen für diese Einheit statisch sind (weshalb ich oben Anführungszeichen um "global" verwendet habe - sie werden innerhalb der Einheit mehr geteilt FSM, als wirklich global). Wie bei allen Globalen erfordert es Sorgfalt.
Das Transitions-Array definiert dann alle möglichen Übergänge und die Funktionen, die für diese Übergänge aufgerufen werden (einschließlich des letzten Catch-All):
Das bedeutet: Wenn Sie sich im
ST_INIT
Status befinden und dieEV_KEYPRESS
Veranstaltung erhalten, rufen Sie anGotKey
.Die Arbeitsweise des FSM wird dann zu einer relativ einfachen Schleife:
Beachten Sie, wie oben erwähnt, die Verwendung
ST_ANY
als Platzhalter, damit ein Ereignis eine Funktion aufrufen kann, unabhängig vom aktuellen Status.EV_ANY
funktioniert auch ähnlich, sodass jedes Ereignis in einem bestimmten Status eine Funktion aufrufen kann.Es kann auch garantiert werden, dass am Ende des Übergangsarrays eine Fehlermeldung angezeigt wird, dass Ihr FSM nicht korrekt erstellt wurde (mithilfe der
ST_ANY/EV_ANY
Kombination).Ich habe ähnlichen Code für viele Kommunikationsprojekte verwendet, z. B. für die frühzeitige Implementierung von Kommunikationsstapeln und -protokollen für eingebettete Systeme. Der große Vorteil war seine Einfachheit und relative Leichtigkeit beim Ändern des Übergangsarrays.
Ich habe keinen Zweifel, dass es Abstraktionen auf höherer Ebene geben wird, die heutzutage vielleicht besser geeignet sind, aber ich vermute, dass sie alle auf dieselbe Art von Struktur hinauslaufen werden.
Und wie
ldog
in einem Kommentar angegeben, können Sie die Globalen insgesamt vermeiden, indem Sie einen Strukturzeiger an alle Funktionen übergeben (und diesen in der Ereignisschleife verwenden). Auf diese Weise können mehrere Zustandsautomaten störungsfrei nebeneinander ausgeführt werden.Erstellen Sie einfach einen Strukturtyp, der die maschinenspezifischen Daten enthält (Status mindestens), und verwenden Sie diese anstelle der globalen Daten.
Der Grund, warum ich das selten getan habe, ist einfach, dass die meisten von mir geschriebenen Zustandsautomaten Singleton-Typen waren (einmalig, beim Start des Prozesses, Lesen von Konfigurationsdateien zum Beispiel), die nicht mehr als eine Instanz ausführen müssen . Aber es hat Wert, wenn Sie mehr als eine ausführen müssen.
quelle
int (*fn)(void*);
wovoid*
der Zeiger auf die Daten ist , dass jede Zustandsfunktion in als Parameter annimmt. Dann können die Statusfunktionen die Daten entweder verwenden oder ignorieren.Die anderen Antworten sind gut, aber eine sehr "leichte" Implementierung, die ich verwendet habe, wenn die Zustandsmaschine sehr einfach ist, sieht aus wie:
Ich würde dies verwenden, wenn die Zustandsmaschine so einfach ist, dass der Ansatz des Funktionszeigers und der Zustandsübergangstabelle übertrieben ist. Dies ist häufig nützlich, um Zeichen für Zeichen oder Wort für Wort zu analysieren.
quelle
Verzeihen Sie mir, dass ich gegen jede Regel in der Informatik verstoßen habe, aber eine Zustandsmaschine ist eine der wenigen Stellen (ich kann nur zwei
goto
ohne weiteres zählen), an denen eine Aussage nicht nur effizienter ist, sondern auch Ihren Code sauberer und leichter lesbar macht. weilgoto
Anweisungen auf Beschriftungen basieren, können Sie Ihre Zustände benennen, anstatt ein Durcheinander von Zahlen verfolgen oder eine Aufzählung verwenden zu müssen. Es sorgt auch für viel saubereren Code, da Sie nicht alle zusätzlichen Funktionszeiger oder riesigen switch-Anweisungen und while-Schleifen benötigen. Habe ich schon erwähnt, dass es auch effizienter ist?So könnte eine Zustandsmaschine aussehen:
Sie bekommen die allgemeine Idee. Der Punkt ist, dass Sie die Zustandsmaschine auf eine effiziente Art und Weise implementieren können, die relativ einfach zu lesen ist und den Leser anschreit, dass er eine Zustandsmaschine betrachtet. Beachten Sie, dass Sie bei der Verwendung von
goto
Anweisungen dennoch vorsichtig sein müssen, da es sehr einfach ist, sich dabei in den Fuß zu schießen.quelle
Sie könnten den State Machine Compiler http://smc.sourceforge.net/ in Betracht ziehen.
Dieses großartige Open Source-Dienstprogramm akzeptiert eine Beschreibung einer Zustandsmaschine in einer einfachen Sprache und kompiliert sie in eine von etwa einem Dutzend Sprachen - einschließlich C und C ++. Das Dienstprogramm selbst ist in Java geschrieben und kann als Teil eines Builds enthalten sein.
Der Grund dafür ist, dass die zugrunde liegende Struktur, sobald Ihre Zustandsmaschine als Code ausgedrückt wird, dazu neigt, unter dem Gewicht der Kesselplatte zu verschwinden, die generiert werden muss, um sie zu unterstützen, anstatt sie mit dem GoF-Statusmuster oder einem anderen Ansatz von Hand zu codieren. Wenn Sie diesen Ansatz verwenden, erhalten Sie eine hervorragende Trennung von Bedenken und halten die Struktur Ihrer Zustandsmaschine "sichtbar". Der automatisch generierte Code wird in Module eingeteilt, die Sie nicht berühren müssen, damit Sie zurückgehen und an der Struktur der Zustandsmaschine herumspielen können, ohne den von Ihnen geschriebenen unterstützenden Code zu beeinträchtigen.
Tut mir leid, ich bin überbegeistert und schrecken zweifellos alle ab. Aber es ist ein erstklassiges Dienstprogramm und auch gut dokumentiert.
quelle
Überprüfen Sie unbedingt die Arbeit von Miro Samek (Blog State Space , Website State Machines & Tools ) an, dessen Artikel im C / C ++ Users Journal großartig waren.
Die Website enthält eine vollständige (C / C ++) Implementierung eines State Machine Frameworks (QP Framework) , eines Event Handlers (QEP) in Open Source und kommerzieller Lizenz. , ein Grundmodellierungswerkzeug (QM) und Tracing - Tool (QSpy) die Erlauben Sie das Zeichnen von Zustandsautomaten, das Erstellen von Code und das Debuggen dieser.
Das Buch enthält eine ausführliche Erklärung zum Was / Warum der Implementierung und deren Verwendung und ist auch ein großartiges Material, um die Grundlagen hierachischer und endlicher Zustandsmaschinen zu verstehen.
Die Website enthält auch Links zu verschiedenen Board-Support-Paketen für die Verwendung der Software mit eingebetteten Plattformen.
quelle
Ich habe etwas Ähnliches getan, wie es paxdiablo beschreibt, nur dass ich anstelle eines Arrays von Zustands- / Ereignisübergängen ein zweidimensionales Array von Funktionszeigern eingerichtet habe, wobei der Ereigniswert als Index einer Achse und der aktuelle Statuswert als das andere. Dann rufe ich einfach an
state = state_table[event][state](params)
und das Richtige passiert. Zellen, die ungültige Zustands- / Ereigniskombinationen darstellen, erhalten natürlich einen Zeiger auf eine Funktion, die dies sagt.Dies funktioniert natürlich nur, wenn die Status- und Ereigniswerte beide zusammenhängende Bereiche sind und bei 0 beginnen oder nahe genug sind.
quelle
#define STATE_LIST() \STATE_LIST_ENTRY(state1)\STATE_LIST_ENTRY(state2)\...
(nach jedem eine implizite neue Zeile\
), in denen Sie das Eingabemakro (neu) definieren, wenn Sie das Makro STATE_LIST verwenden. Beispiel - Erstellen eines Arrays von Statusnamen :#define STATE_LIST_ENTRY(s) #s , \n const char *state_names[] = { STATE_LIST() };\n #undef STATE_LIST_ENTRY
. Einige müssen zuerst eingerichtet werden, aber dies ist äußerst leistungsfähig. Neuen Status hinzufügen -> garantiert keine Fehler.Ein sehr schönes vorlagenbasiertes C ++ - Zustandsmaschinen- "Framework" gibt Stefan Heinzmann in seinem Artikel .
Da der Artikel keinen Link zu einem vollständigen Code-Download enthält, habe ich mir erlaubt, den Code in ein Projekt einzufügen und auszuchecken. Das Zeug unten ist getestet und enthält die wenigen kleinen, aber ziemlich offensichtlichen fehlenden Teile.
Die wichtigste Neuerung dabei ist, dass der Compiler sehr effizienten Code generiert. Leere Ein- / Ausstiegsaktionen sind kostenlos. Nicht leere Ein- / Ausstiegsaktionen sind inline. Der Compiler überprüft auch die Vollständigkeit des Zustandsdiagramms. Fehlende Aktionen erzeugen Verknüpfungsfehler. Das einzige, was nicht gefangen wird, ist das Vermisste
Top::init
.Dies ist eine sehr schöne Alternative zur Implementierung von Miro Samek, wenn Sie ohne das, was fehlt, leben können - dies ist weit entfernt von einer vollständigen UML-Statechart-Implementierung, obwohl die UML-Semantik korrekt implementiert wird, während der Code von Samek vom Design her nicht das Beenden / Übergehen behandelt / Eingabeaktionen in der richtigen Reihenfolge.
Wenn dieser Code genau das tut, was Sie tun müssen, und Sie einen anständigen C ++ - Compiler für Ihr System haben, ist er wahrscheinlich leistungsfähiger als die C / C ++ - Implementierung von Miro. Der Compiler generiert für Sie eine abgeflachte O (1) -Übergangszustandsmaschinenimplementierung. Wenn die Prüfung der Baugruppenausgabe bestätigt, dass die Optimierungen wie gewünscht funktionieren, nähern Sie sich der theoretischen Leistung. Das Beste daran: Es ist relativ kleiner, leicht verständlicher Code.
Testcode folgt.
quelle
Die Technik, die ich für Zustandsautomaten mag (zumindest für die Programmsteuerung), besteht darin, Funktionszeiger zu verwenden. Jeder Zustand wird durch eine andere Funktion dargestellt. Die Funktion nimmt ein Eingabesymbol und gibt den Funktionszeiger für den nächsten Status zurück. Die zentralen Dispatch-Loop-Monitore nehmen die nächste Eingabe auf, führen sie in den aktuellen Status ein und verarbeiten das Ergebnis.
Die Eingabe darauf wird etwas seltsam, da C keine Möglichkeit hat, Typen von Funktionszeigern anzugeben, die sich selbst zurückgeben, sodass die Statusfunktionen zurückkehren
void*
. Aber Sie können so etwas tun:Dann können Ihre einzelnen Statusfunktionen ihre Eingabe einschalten, um den entsprechenden Wert zu verarbeiten und zurückzugeben.
quelle
Einfachster Fall
Punkte: Der Status ist privat, nicht nur für die Kompilierungseinheit, sondern auch für den event_handler. Sonderfälle können getrennt vom Hauptschalter unter Verwendung eines als notwendig erachteten Konstrukts behandelt werden.
Komplexerer Fall
Wenn der Schalter größer als ein paar Bildschirme ist, teilen Sie ihn in Funktionen auf, die jeden Status behandeln, und verwenden Sie eine Statustabelle, um die Funktion direkt nachzuschlagen. Der Staat ist für den Event-Handler weiterhin privat. Die Statushandlerfunktionen geben den nächsten Status zurück. Bei Bedarf können einige Ereignisse im Hauptereignishandler noch eine Sonderbehandlung erhalten. Ich mag es, Pseudo-Ereignisse für den Ein- und Ausgang des Zustands und vielleicht für den Start der Zustandsmaschine einzuwerfen:
Ich bin mir nicht sicher, ob ich die Syntax festgelegt habe, insbesondere in Bezug auf das Array von Funktionszeigern. Ich habe nichts davon durch einen Compiler ausgeführt. Bei der Überprüfung habe ich festgestellt, dass ich vergessen habe, den nächsten Status beim Behandeln der Pseudoereignisse (die (leere) Klammer vor dem Aufruf von state_handler ()) explizit zu verwerfen. Dies ist etwas, das ich gerne mache, auch wenn Compiler die Auslassung stillschweigend akzeptieren. Es teilt den Lesern des Codes mit, dass "Ja, ich wollte die Funktion tatsächlich aufrufen, ohne den Rückgabewert zu verwenden", und es kann statische Analysewerkzeuge daran hindern, davor zu warnen. Es mag eigenwillig sein, weil ich mich nicht daran erinnere, dass jemand anderes dies getan hat.
Punkte: Durch Hinzufügen eines kleinen Teils der Komplexität (Überprüfen, ob der nächste Status vom aktuellen Status abweicht) können Sie doppelten Code an anderer Stelle vermeiden, da die Statusbehandlungsfunktionen die Pseudoereignisse genießen können, die auftreten, wenn ein Status eingegeben und verlassen wird. Denken Sie daran, dass sich der Status beim Behandeln der Pseudoereignisse nicht ändern kann, da das Ergebnis des Statushandlers nach diesen Ereignissen verworfen wird. Sie können das Verhalten natürlich ändern.
Ein State Handler würde so aussehen:
Mehr Komplexität
Wenn die Kompilierungseinheit zu groß wird (was auch immer Sie denken, ich sollte ungefähr 1000 Zeilen sagen), legen Sie jeden Statushandler in einer separaten Datei ab. Wenn jeder Statushandler länger als ein paar Bildschirme ist, teilen Sie jedes Ereignis in einer separaten Funktion auf, ähnlich wie der Statusschalter aufgeteilt wurde. Sie können dies auf verschiedene Arten tun, getrennt vom Status oder mithilfe einer gemeinsamen Tabelle oder durch Kombinieren verschiedener Schemata. Einige von ihnen wurden hier von anderen behandelt. Sortieren Sie Ihre Tabellen und verwenden Sie die binäre Suche, wenn Geschwindigkeit erforderlich ist.
Generische Programmierung
Ich möchte, dass der Präprozessor Probleme wie das Sortieren von Tabellen oder sogar das Generieren von Zustandsautomaten aus Beschreibungen behandelt, damit Sie "Programme über Programme schreiben" können. Ich glaube, dafür nutzen die Boost-Leute C ++ - Vorlagen, aber ich finde die Syntax kryptisch.
Zweidimensionale Tabellen
Ich habe in der Vergangenheit Status- / Ereignistabellen verwendet, aber ich muss sagen, dass ich sie für die einfachsten Fälle nicht für notwendig halte und die Klarheit und Lesbarkeit der switch-Anweisung bevorzuge, auch wenn sie sich über einen vollen Bildschirm hinaus erstreckt. In komplexeren Fällen geraten die Tabellen schnell außer Kontrolle, wie andere angemerkt haben. Mit den hier vorgestellten Redewendungen können Sie eine Reihe von Ereignissen und Zuständen hinzufügen, wenn Sie Lust dazu haben, ohne eine speicherintensive Tabelle verwalten zu müssen (auch wenn es sich möglicherweise um Programmspeicher handelt).
Haftungsausschluss
Spezielle Bedürfnisse mögen diese Redewendungen weniger nützlich machen, aber ich habe festgestellt, dass sie sehr klar und wartbar sind.
quelle
Extrem ungetestet, aber es macht Spaß, Code zu schreiben, jetzt in einer verfeinerten Version als meine ursprüngliche Antwort; Aktuelle Versionen finden Sie unter mercurial.intuxication.org :
sm.h.
Beispiel.c
quelle
Die Antwort von paxdiable hat mir sehr gut gefallen und ich habe beschlossen, alle fehlenden Funktionen für meine Anwendung wie Schutzvariablen und zustandsmaschinenspezifische Daten zu implementieren.
Ich habe meine Implementierung auf diese Site hochgeladen, um sie mit der Community zu teilen. Es wurde mit IAR Embedded Workbench für ARM getestet.
https://sourceforge.net/projects/compactfsm/
quelle
Ein weiteres interessantes Open Source Tool ist Yakindu Statechart Tools auf statecharts.org . Es verwendet Harel-Zustandsdiagramme und stellt somit hierarchische und parallele Zustände bereit und generiert C- und C ++ - (sowie Java-) Code. Es werden keine Bibliotheken verwendet, sondern es wird ein "einfacher Code" -Ansatz verfolgt. Der Code wendet grundsätzlich Switch-Case-Strukturen an. Die Codegeneratoren können auch angepasst werden. Zusätzlich bietet das Tool viele weitere Funktionen.
quelle
Ich komme zu spät (wie üblich), scanne aber die bisherigen Antworten und denke, dass etwas Wichtiges fehlt.
Ich habe in meinen eigenen Projekten festgestellt, dass es sehr hilfreich sein kann, nicht für jede gültige Kombination aus Status und Ereignis eine Funktion zu haben. Ich mag die Idee, effektiv eine 2D-Tabelle mit Zuständen / Ereignissen zu haben. Aber ich mag es, wenn die Tabellenelemente mehr als ein einfacher Funktionszeiger sind. Stattdessen versuche ich, mein Design so zu organisieren, dass es im Kern eine Reihe einfacher atomarer Elemente oder Aktionen enthält. Auf diese Weise kann ich diese einfachen atomaren Elemente an jedem Schnittpunkt meiner Status- / Ereignistabelle auflisten. Die Idee ist, dass Sie nicht Masse von N quadratischen (normalerweise sehr einfachen) Funktionen definieren müssen. Warum etwas so fehleranfällig, zeitaufwändig, schwer zu schreiben, schwer zu lesen, wie Sie es nennen?
Ich füge auch einen optionalen neuen Status und einen optionalen Funktionszeiger für jede Zelle in der Tabelle hinzu. Der Funktionszeiger ist für Ausnahmefälle vorgesehen, in denen Sie nicht nur eine Liste atomarer Aktionen auslösen möchten.
Sie wissen, dass Sie es richtig machen, wenn Sie viele verschiedene Funktionen ausdrücken können, indem Sie einfach Ihre Tabelle bearbeiten und keinen neuen Code schreiben müssen.
quelle
Alrght, ich denke, meine ist nur ein bisschen anders als die aller anderen. Etwas mehr Trennung von Code und Daten als ich in den anderen Antworten sehe. Ich habe mich wirklich über die Theorie informiert, um dies zu schreiben, die eine vollständige reguläre Sprache implementiert (leider ohne reguläre Ausdrücke). Ullman, Minsky, Chomsky. Ich kann nicht sagen, dass ich alles verstanden habe, aber ich habe mich so direkt wie möglich an die alten Meister gewandt: durch ihre Worte.
Ich verwende einen Funktionszeiger auf ein Prädikat, das den Übergang in einen "Ja" -Zustand oder einen "Nein" -Zustand bestimmt. Dies erleichtert die Erstellung eines Endlichzustandsakzeptors für eine reguläre Sprache, die Sie in einer Assemblersprachen-ähnlichen Weise programmieren. Bitte lassen Sie sich nicht von meinen dummen Namenswahlen abschrecken. 'czek' == 'check'. 'grok' == [schau im Hacker Dictionary nach].
Daher ruft czek für jede Iteration eine Prädikatfunktion mit dem aktuellen Zeichen als Argument auf. Wenn das Prädikat true zurückgibt, wird das Zeichen verbraucht (der Zeiger ist vorgerückt) und wir folgen dem Übergang 'y', um den nächsten Status auszuwählen. Wenn das Prädikat false zurückgibt, wird das Zeichen NICHT verbraucht und wir folgen dem Übergang 'n'. Jeder Befehl ist also ein Zwei-Wege-Zweig! Ich muss damals The Story of Mel gelesen haben.
Dieser Code stammt direkt von meinem Postscript-Interpreter und hat sich mit viel Anleitung der Kollegen auf comp.lang.c. zu seiner aktuellen Form entwickelt. Da Postscript im Grunde keine Syntax hat (nur ausgeglichene Klammern erforderlich), fungiert ein solcher regulärer Sprachakzeptor auch als Parser.
quelle
boost.org wird mit 2 verschiedenen Zustandsdiagrammimplementierungen geliefert:
Wie immer bringt Sie Boost in die Hölle der Vorlagen.
Die erste Bibliothek ist für leistungskritischere Zustandsautomaten vorgesehen. Die zweite Bibliothek bietet Ihnen einen direkten Übergangspfad von einem UML-Zustandsdiagramm zum Code.
Hier ist die SO-Frage, die nach einem Vergleich zwischen den beiden fragt, auf die beide Autoren antworten.
quelle
Diese Reihe von Ars OpenForum-Beiträgen zu einem etwas komplizierten Teil der Steuerlogik enthält eine sehr einfach zu verfolgende Implementierung als Zustandsmaschine in C.
quelle
Hab das irgendwo gesehen
quelle
goto
dieser Makros schafft eine Abhängigkeit von einem vorläufigen Multitasking-Betriebssystem.Angesichts der Tatsache, dass Sie implizieren, dass Sie C ++ und damit OO-Code verwenden können, würde ich vorschlagen, das 'GoF'-Zustandsmuster zu bewerten (GoF = Gang of Four, die Leute, die das Designmusterbuch geschrieben haben, das Designmuster ins Rampenlicht gerückt hat).
Es ist nicht besonders komplex und wird häufig verwendet und diskutiert, sodass Beispiele und Erklärungen online leicht zu sehen sind.
Es wird wahrscheinlich auch von anderen Personen erkannt werden, die Ihren Code zu einem späteren Zeitpunkt pflegen.
Wenn Effizienz die Sorge ist, lohnt es sich, tatsächlich ein Benchmarking durchzuführen, um sicherzustellen, dass ein Nicht-OO-Ansatz effizienter ist, da viele Faktoren die Leistung beeinflussen und es nicht immer einfach OO-schlechter, funktionaler Code gut ist. Wenn die Speichernutzung für Sie eine Einschränkung darstellt, lohnt es sich erneut, einige Tests oder Berechnungen durchzuführen, um festzustellen, ob dies tatsächlich ein Problem für Ihre spezielle Anwendung darstellt, wenn Sie das Statusmuster verwenden.
Das Folgende sind einige Links zum 'Gof'-Zustandsmuster, wie Craig vorschlägt:
quelle
Hier ist ein Beispiel für eine Finite State Machine für Linux, die Nachrichtenwarteschlangen als Ereignisse verwendet. Die Ereignisse werden in die Warteschlange gestellt und der Reihe nach behandelt. Der Status ändert sich je nachdem, was für jedes Ereignis passiert.
Dies ist ein Beispiel für eine Datenverbindung mit Zuständen wie:
Eine kleine zusätzliche Funktion, die ich hinzugefügt habe, war ein Zeitstempel für jede Nachricht / jedes Ereignis. Der Ereignishandler ignoriert Ereignisse, die zu alt sind (sie sind abgelaufen). Dies kann in der realen Welt häufig vorkommen, wenn Sie unerwartet in einem Zustand stecken bleiben.
Dieses Beispiel läuft unter Linux. Verwenden Sie das folgende Makefile, um es zu kompilieren und damit herumzuspielen.
state_machine.c
Makefile
quelle
Ihre Frage ist ziemlich allgemein gehalten.
Hier sind zwei Referenzartikel, die nützlich sein könnten.
Implementierung einer eingebetteten Zustandsmaschine
quelle
Ich habe State Machine Compiler in Java- und Python-Projekten mit Erfolg eingesetzt.
quelle
Dies ist ein alter Beitrag mit vielen Antworten, aber ich dachte, ich würde meinen eigenen Ansatz zur endlichen Zustandsmaschine in C hinzufügen. Ich habe ein Python-Skript erstellt, um den Skelett-C-Code für eine beliebige Anzahl von Zuständen zu erzeugen. Dieses Skript ist auf GituHub bei FsmTemplateC dokumentiert
Dieses Beispiel basiert auf anderen Ansätzen, über die ich gelesen habe. Es werden keine goto- oder switch-Anweisungen verwendet, sondern Übergangsfunktionen in einer Zeigermatrix (Nachschlagetabelle). Der Code basiert auf einem großen mehrzeiligen Initialisierungsmakro und C99-Funktionen (bezeichnete Initialisierer und zusammengesetzte Literale). Wenn Ihnen diese Dinge nicht gefallen, wird Ihnen dieser Ansatz möglicherweise nicht gefallen.
Hier ist ein Python-Skript eines Drehkreuzbeispiels, das mit FsmTemplateC Skelett-C-Code generiert :
Der generierte Ausgabekopf enthält die Typedefs:
eFsmTurnstileCheck
wird verwendet, um zu bestimmen, ob ein Übergang blockiert wurde, mit dem fortfahrenEFSM_TURNSTILE_TR_RETREAT
darfEFSM_TURNSTILE_TR_ADVANCE
oder dem Funktionsaufruf kein Übergang mit vorangestellt wurdeEFSM_TURNSTILE_TR_CONTINUE
.eFsmTurnstileState
ist einfach die Liste der Staaten.eFsmTurnstileInput
ist einfach die Liste der Eingaben.FsmTurnstile
Struktur ist das Herzstück der Zustandsmaschine mit der Übergangsprüfung, der Funktionsnachschlagetabelle, dem aktuellen Zustand, dem befohlenen Zustand und einem Alias für die primäre Funktion, die die Maschine ausführt.FsmTurnstile
sollte nur von der Struktur aufgerufen werden und muss seine erste Eingabe als Zeiger auf sich selbst haben, um einen dauerhaften, objektorientierten Stil beizubehalten.Nun zu den Funktionsdeklarationen im Header:
Funktionsnamen haben das Format
{prefix}_{from}_{to}
, wobei{from}
der vorherige (aktuelle) Status und{to}
der nächste Status ist. Beachten Sie, dass ein NULL-Zeiger anstelle eines Funktionszeigers gesetzt wird, wenn die Übergangstabelle bestimmte Übergänge nicht zulässt. Schließlich geschieht die Magie mit einem Makro. Hier erstellen wir die Übergangstabelle (Matrix von Zustandsaufzählungen) und die Nachschlagetabelle für Zustandsübergangsfunktionen (eine Matrix von Funktionszeigern):Beim Erstellen des FSM das Makro
FSM_EXAMPLE_CREATE()
verwendet werden.Jetzt sollte im Quellcode jede oben deklarierte Zustandsübergangsfunktion ausgefüllt werden. Die
FsmTurnstileFopts
Struktur kann verwendet werden, um Daten an / von der Zustandsmaschine zu übergeben. Jeder Übergang muss gleich eingestellt seinfsm->check
, um entwederEFSM_EXAMPLE_TR_RETREAT
den Übergang zu blockieren oderEFSM_EXAMPLE_TR_ADVANCE
den Übergang in den befohlenen Zustand zu ermöglichen. Ein funktionierendes Beispiel finden Sie unter (FsmTemplateC) [ https://github.com/ChisholmKyle/FsmTemplateC] .Hier ist die sehr einfache tatsächliche Verwendung in Ihrem Code:
Das ganze Header-Geschäft und all diese Funktionen, nur um eine einfache und schnelle Oberfläche zu haben, ist es mir wert.
quelle
Sie können die Open Source-Bibliothek OpenFST verwenden .
quelle
quelle
Ich persönlich verwende selbstreferenzierende Strukturen in Kombination mit Zeigerarrays. Ich habe vor einiger Zeit ein Tutorial auf Github hochgeladen, Link:
https://github.com/mmelchger/polling_state_machine_c
Hinweis: Mir ist klar, dass dieser Thread ziemlich alt ist, aber ich hoffe, Input und Gedanken zum Design der Zustandsmaschine zu erhalten und ein Beispiel für ein mögliches Design der Zustandsmaschine in C liefern zu können.
quelle
Sie können UML-state-machine-in-c als "leichtes" State-Machine-Framework in C betrachten. Ich habe dieses Framework geschrieben, um beide Finite-State-Machine zu unterstützen als auch die Hierarchical-State-Machine zu unterstützen . Im Vergleich zu Statustabellen oder einfachen Switch-Fällen ist ein Framework-Ansatz skalierbarer. Es kann für einfache endliche Zustandsmaschinen bis hin zu komplexen hierarchischen Zustandsmaschinen verwendet werden.
Zustandsmaschine wird durch
state_machine_t
Struktur dargestellt. Es enthält nur zwei Mitglieder "Event" und einen Zeiger auf "state_t".state_machine_t
muss das erste Mitglied Ihrer Zustandsmaschinenstruktur sein. z.Bstate_t
enthält einen Handler für den Status sowie optionale Handler für die Ein- und Ausstiegsaktion.Wenn das Framework für eine hierarchische Zustandsmaschine konfiguriert ist, wird die
state_t
enthält die einen Zeiger auf den übergeordneten und den untergeordneten Status.Framework bietet eine API
dispatch_event
zum Versenden des Ereignisses an die Zustandsmaschine undswitch_state
zum Auslösen des Zustandsübergangs.Weitere Informationen zum Implementieren einer hierarchischen Zustandsmaschine finden Sie im GitHub Repository.
Codebeispiele,
https://github.com/kiishor/UML-State-Machine-in-C/blob/master/demo/simple_state_machine/readme.md https://github.com/kiishor/UML-State-Machine-in-C /blob/master/demo/simple_state_machine_enhanced/readme.md
quelle
Hier ist eine Methode für eine Zustandsmaschine, die Makros verwendet, sodass jede Funktion ihre eigenen Zustände haben kann: https://www.codeproject.com/Articles/37037/Macros-to-simulate-multi-tasking-blocking-code -beim
Es trägt den Titel "Multitasking simulieren", aber das ist nicht die einzige Verwendung.
Diese Methode verwendet Rückrufe, um jede Funktion dort aufzunehmen, wo sie aufgehört hat. Jede Funktion enthält eine Liste von Zuständen, die für jede Funktion eindeutig sind. Eine zentrale "Leerlaufschleife" wird verwendet, um die Zustandsmaschinen auszuführen. Die "Leerlaufschleife" hat keine Ahnung, wie die Zustandsmaschinen funktionieren, es sind die einzelnen Funktionen, die "wissen, was zu tun ist". Um Code für die Funktionen zu schreiben, erstellt man einfach eine Liste von Zuständen und verwendet die Makros, um "anzuhalten" und "fortzusetzen". Ich habe diese Makros bei Cisco verwendet, als ich die Transceiver-Bibliothek für den Nexus 7000-Switch geschrieben habe.
quelle