質問

私の作小さなプロジェクト混合CおよびC++.今しばらくお待さっぽ状態機械の一つの私のワーカスレです。

破壊も修復もおもしろくない日付になっては思いを共有状態-機械設計技術です。

注意: 私は、主に後試&試験を実施。

更新: に基づくすべての入力が集結しましたので、私は着このアーキテクチャ:

An event pump points to an event integrator which points to a dispatcher. The dispatcher points to 1 through n actions which point back to the event integrator. A transition table with wildcards points to the dispatcher.

役に立ちましたか?

解決

以前に設計したステートマシン(C ++ではなくC)は、すべて struct 配列とループになりました。構造は基本的に、状態とイベント(ルックアップ用)、および次のような新しい状態を返す関数で構成されます。

typedef struct {
    int st;
    int ev;
    int (*fn)(void);
} tTransition;

次に、単純な定義で状態とイベントを定義します( Any は特別なマーカーです。以下を参照):

#define ST_ANY              -1
#define ST_INIT              0
#define ST_ERROR             1
#define ST_TERM              2
: :
#define EV_ANY              -1
#define EV_KEYPRESS       5000
#define EV_MOUSEMOVE      5001

次に、トランジションによって呼び出されるすべての関数を定義します:

static int GotKey (void) { ... };
static int FsmError (void) { ... };

これらの関数はすべて、変数を受け取らず、ステートマシンの新しい状態を返すように記述されています。この例では、グローバル変数を使用して、必要に応じて状態関数に情報を渡します。

グローバルの使用は、FSMが通常単一のコンパイルユニット内でロックされ、すべての変数がそのユニットに対して静的であるため、見た目ほど悪くありません(これが、上記の「グローバル」を引用符で囲んだ理由です-真にグローバルなものよりも、FSM内でより共有されています)。すべてのグローバルと同様に、注意が必要です。

transitions配列は、可能なすべての遷移と、それらの遷移に対して呼び出される関数(最後のすべてを含む)を定義します:

tTransition trans[] = {
    { ST_INIT, EV_KEYPRESS, &GotKey},
    : :
    { ST_ANY, EV_ANY, &FsmError}
};
#define TRANS_COUNT (sizeof(trans)/sizeof(*trans))

つまり、 ST_INIT 状態で EV_KEYPRESS イベントを受け取った場合、 GotKey を呼び出します。

FSMの動作は比較的単純なループになります:

state = ST_INIT;
while (state != ST_TERM) {
    event = GetNextEvent();
    for (i = 0; i < TRANS_COUNT; i++) {
        if ((state == trans[i].st) || (ST_ANY == trans[i].st)) {
            if ((event == trans[i].ev) || (EV_ANY == trans[i].ev)) {
                state = (trans[i].fn)();
                break;
            }
        }
    }
}

上記で示唆したように、ワイルドカードとしての ST_ANY の使用に注意してください。これにより、現在の状態に関係なくイベントが関数を呼び出すことができます。 EV_ANY も同様に機能し、特定の状態のイベントが関数を呼び出すことを許可します。

また、transitions配列の最後に到達すると、FSMが正しく構築されていないことを示すエラーが表示されることを保証できます( ST_ANY / EV_ANY の組み合わせを使用します。

これに似たコードを、組み込みシステムの通信スタックやプロトコルの初期実装など、非常に多くの通信プロジェクトで使用しました。大きな利点は、そのシンプルさと、トランジション配列の変更が比較的簡単だったことです。

今日、より適切な高レベルの抽象化が存在することは疑いありませんが、これらはすべて同じ種類の構造に要約されると思われます。


また、 ldog がコメントで述べているように、構造体のポインターをすべての関数に渡す(およびイベントループでそれを使用する)ことで、グローバルを完全に回避できます。これにより、複数のステートマシンが干渉なしに並行して実行できるようになります。

マシン固有のデータ(最低限の状態)を保持する構造タイプを作成し、グローバルの代わりにそれを使用します。

これまでほとんど行わなかった理由は、記述したステートマシンのほとんどがシングルトンタイプ(たとえば、1回限り、プロセス開始時、構成ファイルの読み取り)であり、これ以上実行する必要がないためです。複数のインスタンス。ただし、複数実行する必要がある場合には価値があります。

他のヒント

他の回答は良いですが、非常に「軽量」ですステートマシンが非常にシンプルな場合に使用した実装:

enum state { ST_NEW, ST_OPEN, ST_SHIFT, ST_END };

enum state current_state = ST_NEW;

while (current_state != ST_END)
{
    input = get_input();

    switch (current_state)
    {
        case ST_NEW:
        /* Do something with input and set current_state */
        break;

        case ST_OPEN:
        /* Do something different and set current_state */
        break;

        /* ... etc ... */
    }
}

ステートマシンが十分にシンプルで、関数ポインター&amp;状態遷移表のアプローチは過剰です。これは、文字ごとまたは単語ごとの解析に役立つことがよくあります。

コンピューターサイエンスのすべての規則に違反したことはご容赦ください。ただし、ステートマシンは、 goto ステートメントがより効率的であるだけでなく(手元に2つしか数えられない)数少ない場所の1つです。また、コードがよりクリーンで読みやすくなります。 goto ステートメントはラベルに基づいているため、大量の数字を追跡したり、enumを使用したりする代わりに、状態に名前を付けることができます。また、関数ポインターや巨大なswitchステートメントやwhileループの余分な部分をすべて必要としないので、コードがずっときれいになります。私もそれがより効率的だと言いましたか?

ステートマシンは次のようになります。

void state_machine() {
first_state:
    // Do some stuff here
    switch(some_var) {
    case 0:
        goto first_state;
    case 1:
        goto second_state;
    default:
        return;
    }

second_state:
    // Do some stuff here
    switch(some_var) {
    case 0:
        goto first_state;
    case 1:
        goto second_state;
    default:
        return;
    }
}

一般的なアイデアが得られます。重要なのは、ステートマシンを効率的な方法で実装でき、比較的読みやすく、ステートマシンを見ていることをリーダーに叫ぶ方法です。 goto ステートメントを使用している場合は、気をつけてください。そうすると、自分で足を撃ちやすくなります。

State Machine Compiler http://smc.sourceforge.netを検討してください。 /

この素晴らしいオープンソースユーティリティは、シンプルな言語でのステートマシンの記述を受け入れ、CやC ++を含む12以上の言語のいずれかにコンパイルします。ユーティリティ自体はJavaで記述されており、ビルドの一部として含めることができます。

これを行う理由は、GoF状態パターンまたはその他のアプローチを使用して手動でコーディングするのではなく、ステートマシンがコードとして表現されると、生成する必要がある定型的な重みの下で、基礎となる構造が消える傾向があるためですそれをサポートします。このアプローチを使用すると、関心事を優れた方法で分離でき、ステートマシンの構造を「見える」状態に保ちます。自動生成されたコードは、触れる必要のないモジュールに組み込まれるため、作成したサポートコードに影響を与えることなく、ステートマシンの構造に戻って操作することができます。

申し訳ありませんが、私は熱心であり、間違いなく皆を先送りにします。しかし、それは一流のユーティリティであり、十分に文書化されています。

Miro Samek(ブログ State Space 、ウェブサイト State Machines&amp; Tools )、 C / C ++ Users Journal の記事は素晴らしい。

ウェブサイトには、ステートマシンフレームワーク(QPフレームワーク)イベントハンドラー(QEP)のオープンソースと商用ライセンスの両方での完全な(C / C ++)実装が含まれています>、基本モデリングツール(QM)およびトレースツール(QSpy)。これらにより、ステートマシンの描画、コードの作成、デバッグが可能になります。

この本には、実装の理由/理由とその使用方法に関する広範な説明が含まれており、階層型および有限状態マシンの基礎を理解するための優れた資料でもあります。

ウェブサイトには、組み込みプラットフォームでソフトウェアを使用するためのいくつかのボードサポートパッケージへのリンクも含まれています。

paxdiabloの説明と同様のことを行いました。状態/イベント遷移の配列の代わりに、イベント値を1つの軸と現在のインデックスとして、関数ポインターの2次元配列を設定しました。もう一方の状態値。次に、 state = state_table [event] [state](params)を呼び出すだけで、正しいことが起こります。無効な状態/イベントの組み合わせを表すセルは、もちろんそう言う関数へのポインターを取得します。

明らかに、これは、状態とイベントの値が両方とも連続した範囲であり、0から始まるか十分に近い場合にのみ機能します。

非常に優れたテンプレートベースのC ++ステートマシン&quot; framework&quot; Stefan Heinzmannの記事で提供されています。

記事には完全なコードダウンロードへのリンクがないため、自由にコードをプロジェクトに貼り付けてチェックアウトしました。以下のものはテストされており、いくつかのマイナーではありますが、明らかな欠落している部分が含まれています。

ここでの主要な革新は、コンパイラが非常に効率的なコードを生成していることです。空の入退場アクションには費用はかかりません。空ではない入場/退場アクションはインライン化されます。コンパイラは、ステートチャートの完全性も検証しています。欠落しているアクションはリンクエラーを生成します。キャッチされない唯一のものは、欠落している Top :: init です。

これはMiro Samekの実装の非常に優れた代替手段です。不足しているものなしで生きることができれば-これは完全なUML Statechart実装とはほど遠いですが、UMLセマンティクスを正しく実装しますが、Samekのコードは設計上処理しません正しい順序でexit / transition / entryアクション。

このコードがあなたがする必要があるもののために機能し、あなたのシステムにまともなC ++コンパイラがあれば、おそらくMiroのC / C ++実装よりもパフォーマンスが良くなります。コンパイラは、平坦化されたO(1)遷移状態マシンの実装を自動的に生成します。アセンブリ出力の監査により、最適化が希望どおりに機能することが確認されると、理論上のパフォーマンスに近づきます。最良の部分:比較的小さく、理解しやすいコードです。

#ifndef HSM_HPP
#define HSM_HPP

// This code is from:
// Yet Another Hierarchical State Machine
// by Stefan Heinzmann
// Overload issue 64 december 2004
// http://accu.org/index.php/journals/252

/* This is a basic implementation of UML Statecharts.
 * The key observation is that the machine can only
 * be in a leaf state at any given time. The composite
 * states are only traversed, never final.
 * Only the leaf states are ever instantiated. The composite
 * states are only mechanisms used to generate code. They are
 * never instantiated.
 */

// Helpers

// A gadget from Herb Sutter's GotW #71 -- depends on SFINAE
template<class D, class B>
class IsDerivedFrom {
    class Yes { char a[1]; };
    class No  { char a[10]; };
    static Yes Test(B*); // undefined
    static No Test(...); // undefined
public:
    enum { Res = sizeof(Test(static_cast<D*>(0))) == sizeof(Yes) ? 1 : 0 };
};

template<bool> class Bool {};

// Top State, Composite State and Leaf State

template <typename H>
struct TopState {
    typedef H Host;
    typedef void Base;
    virtual void handler(Host&) const = 0;
    virtual unsigned getId() const = 0;
};

template <typename H, unsigned id, typename B>
struct CompState;

template <typename H, unsigned id, typename B = CompState<H, 0, TopState<H> > >
struct CompState : B {
    typedef B Base;
    typedef CompState<H, id, Base> This;
    template <typename X> void handle(H& h, const X& x) const { Base::handle(h, x); }
    static void init(H&); // no implementation
    static void entry(H&) {}
    static void exit(H&) {}
};

template <typename H>
struct CompState<H, 0, TopState<H> > : TopState<H> {
    typedef TopState<H> Base;
    typedef CompState<H, 0, Base> This;
    template <typename X> void handle(H&, const X&) const {}
    static void init(H&); // no implementation
    static void entry(H&) {}
    static void exit(H&) {}
};

template <typename H, unsigned id, typename B = CompState<H, 0, TopState<H> > >
struct LeafState : B {
    typedef H Host;
    typedef B Base;
    typedef LeafState<H, id, Base> This;
    template <typename X> void handle(H& h, const X& x) const { Base::handle(h, x); }
    virtual void handler(H& h) const { handle(h, *this); }
    virtual unsigned getId() const { return id; }
    static void init(H& h) { h.next(obj); } // don't specialize this
    static void entry(H&) {}
    static void exit(H&) {}
    static const LeafState obj; // only the leaf states have instances
};

template <typename H, unsigned id, typename B>
const LeafState<H, id, B> LeafState<H, id, B>::obj;

// Transition Object

template <typename C, typename S, typename T>
// Current, Source, Target
struct Tran {
    typedef typename C::Host Host;
    typedef typename C::Base CurrentBase;
    typedef typename S::Base SourceBase;
    typedef typename T::Base TargetBase;
    enum { // work out when to terminate template recursion
        eTB_CB = IsDerivedFrom<TargetBase, CurrentBase>::Res,
        eS_CB = IsDerivedFrom<S, CurrentBase>::Res,
        eS_C = IsDerivedFrom<S, C>::Res,
        eC_S = IsDerivedFrom<C, S>::Res,
        exitStop = eTB_CB && eS_C,
        entryStop = eS_C || eS_CB && !eC_S
    };
    // We use overloading to stop recursion.
    // The more natural template specialization
    // method would require to specialize the inner
    // template without specializing the outer one,
    // which is forbidden.
    static void exitActions(Host&, Bool<true>) {}
    static void exitActions(Host&h, Bool<false>) {
        C::exit(h);
        Tran<CurrentBase, S, T>::exitActions(h, Bool<exitStop>());
    }
    static void entryActions(Host&, Bool<true>) {}
    static void entryActions(Host& h, Bool<false>) {
        Tran<CurrentBase, S, T>::entryActions(h, Bool<entryStop>());
        C::entry(h);
    }
    Tran(Host & h) : host_(h) {
        exitActions(host_, Bool<false>());
    }
    ~Tran() {
        Tran<T, S, T>::entryActions(host_, Bool<false>());
        T::init(host_);
    }
    Host& host_;
};

// Initializer for Compound States

template <typename T>
struct Init {
    typedef typename T::Host Host;
    Init(Host& h) : host_(h) {}
    ~Init() {
        T::entry(host_);
        T::init(host_);
    }
    Host& host_;
};

#endif // HSM_HPP

テストコードが続きます。

#include <cstdio>
#include "hsm.hpp"
#include "hsmtest.hpp"

/* Implements the following state machine from Miro Samek's
 * Practical Statecharts in C/C++
 *
 * |-init-----------------------------------------------------|
 * |                           s0                             |
 * |----------------------------------------------------------|
 * |                                                          |
 * |    |-init-----------|        |-------------------------| |
 * |    |       s1       |---c--->|            s2           | |
 * |    |----------------|<--c----|-------------------------| |
 * |    |                |        |                         | |
 * |<-d-| |-init-------| |        | |-init----------------| | |
 * |    | |     s11    |<----f----| |          s21        | | |
 * | /--| |------------| |        | |---------------------| | |
 * | a  | |            | |        | |                     | | |
 * | \->| |            |------g--------->|-init------|    | | |
 * |    | |____________| |        | |-b->|    s211   |---g--->|
 * |    |----b---^       |------f------->|           |    | | |
 * |    |________________|        | |<-d-|___________|<--e----|
 * |                              | |_____________________| | |
 * |                              |_________________________| |
 * |__________________________________________________________|
 */

class TestHSM;

typedef CompState<TestHSM,0>     Top;
typedef CompState<TestHSM,1,Top>   S0;
typedef CompState<TestHSM,2,S0>      S1;
typedef LeafState<TestHSM,3,S1>        S11;
typedef CompState<TestHSM,4,S0>      S2;
typedef CompState<TestHSM,5,S2>        S21;
typedef LeafState<TestHSM,6,S21>         S211;

enum Signal { A_SIG, B_SIG, C_SIG, D_SIG, E_SIG, F_SIG, G_SIG, H_SIG };

class TestHSM {
public:
    TestHSM() { Top::init(*this); }
    ~TestHSM() {}
    void next(const TopState<TestHSM>& state) {
        state_ = &state;
    }
    Signal getSig() const { return sig_; }
    void dispatch(Signal sig) {
        sig_ = sig;
        state_->handler(*this);
    }
    void foo(int i) {
        foo_ = i;
    }
    int foo() const {
        return foo_;
    }
private:
    const TopState<TestHSM>* state_;
    Signal sig_;
    int foo_;
};

bool testDispatch(char c) {
    static TestHSM test;
    if (c<'a' || 'h'<c) {
        return false;
    }
    printf("Signal<-%c", c);
    test.dispatch((Signal)(c-'a'));
    printf("\n");
    return true;
}

int main(int, char**) {
    testDispatch('a');
    testDispatch('e');
    testDispatch('e');
    testDispatch('a');
    testDispatch('h');
    testDispatch('h');
    return 0;
}

#define HSMHANDLER(State) \
    template<> template<typename X> inline void State::handle(TestHSM& h, const X& x) const

HSMHANDLER(S0) {
    switch (h.getSig()) {
    case E_SIG: { Tran<X, This, S211> t(h);
        printf("s0-E;");
        return; }
    default:
        break;
    }
    return Base::handle(h, x);
}

HSMHANDLER(S1) {
    switch (h.getSig()) {
    case A_SIG: { Tran<X, This, S1> t(h);
        printf("s1-A;"); return; }
    case B_SIG: { Tran<X, This, S11> t(h);
        printf("s1-B;"); return; }
    case C_SIG: { Tran<X, This, S2> t(h);
        printf("s1-C;"); return; }
    case D_SIG: { Tran<X, This, S0> t(h);
        printf("s1-D;"); return; }
    case F_SIG: { Tran<X, This, S211> t(h);
        printf("s1-F;"); return; }
    default: break;
    }
    return Base::handle(h, x);
}

HSMHANDLER(S11) {
    switch (h.getSig()) {
    case G_SIG: { Tran<X, This, S211> t(h);
        printf("s11-G;"); return; }
    case H_SIG: if (h.foo()) {
            printf("s11-H");
            h.foo(0); return;
        } break;
    default: break;
    }
    return Base::handle(h, x);
}

HSMHANDLER(S2) {
    switch (h.getSig()) {
    case C_SIG: { Tran<X, This, S1> t(h);
        printf("s2-C"); return; }
    case F_SIG: { Tran<X, This, S11> t(h);
        printf("s2-F"); return; }
    default: break;
    }
    return Base::handle(h, x);
}

HSMHANDLER(S21) {
    switch (h.getSig()) {
    case B_SIG: { Tran<X, This, S211> t(h);
        printf("s21-B;"); return; }
    case H_SIG: if (!h.foo()) {
            Tran<X, This, S21> t(h);
            printf("s21-H;"); h.foo(1);
            return;
        } break;
    default: break;
    }
    return Base::handle(h, x);
}

HSMHANDLER(S211) {
    switch (h.getSig()) {
    case D_SIG: { Tran<X, This, S21> t(h);
        printf("s211-D;"); return; }
    case G_SIG: { Tran<X, This, S0> t(h);
        printf("s211-G;"); return; }
    }
    return Base::handle(h, x);
}

#define HSMENTRY(State) \
    template<> inline void State::entry(TestHSM&) { \
        printf(#State "-ENTRY;"); \
    }

HSMENTRY(S0)
HSMENTRY(S1)
HSMENTRY(S11)
HSMENTRY(S2)
HSMENTRY(S21)
HSMENTRY(S211)

#define HSMEXIT(State) \
    template<> inline void State::exit(TestHSM&) { \
        printf(#State "-EXIT;"); \
    }

HSMEXIT(S0)
HSMEXIT(S1)
HSMEXIT(S11)
HSMEXIT(S2)
HSMEXIT(S21)
HSMEXIT(S211)

#define HSMINIT(State, InitState) \
    template<> inline void State::init(TestHSM& h) { \
       Init<InitState> i(h); \
       printf(#State "-INIT;"); \
    }

HSMINIT(Top, S0)
HSMINIT(S0, S1)
HSMINIT(S1, S11)
HSMINIT(S2, S21)
HSMINIT(S21, S211)

ステートマシン(少なくともプログラム制御用)で私が気に入っている手法は、関数ポインターを使用することです。各状態は異なる関数で表されます。関数は入力シンボルを取り、次の状態の関数ポインターを返します。中央ディスパッチループモニターは次の入力を受け取り、それを現在の状態に送り、結果を処理します。

Cには自分自身を返す関数ポインタの型を示す方法がないため、その入力は少し奇妙になります。そのため、状態関数は void * を返します。しかし、次のようなことができます:

typedef void* (*state_handler)(input_symbol_t);
void dispatch_fsm()
{
    state_handler current = initial_handler;
    /* Let's assume returning null indicates end-of-machine */
    while (current) {
        current = current(get_input);
    }
 }

その後、個々の状態関数は、適切な値を処理して返すために入力を切り替えることができます。

最も単純なケース

enum event_type { ET_THIS, ET_THAT };
union event_parm { uint8_t this; uint16_t that; }
static void handle_event(enum event_type event, union event_parm parm)
{
  static enum { THIS, THAT } state;
  switch (state)
  {
    case THIS:
    switch (event)
    {
      case ET_THIS:
      // Handle event.
      break;

      default:
      // Unhandled events in this state.
      break;
    }
    break;

    case THAT:
    // Handle state.
    break;
  }
}

ポイント: 状態は、コンパイル単位だけでなくevent_handlerに対してもプライベートです。 特別な場合は、必要と思われる構成を使用して、メインスイッチとは別に処理できます。

より複雑なケース

スイッチが数画面いっぱいになると、状態テーブルを使用して関数を直接検索し、各状態を処理する関数に切り替えます。状態はまだイベントハンドラーに対してプライベートです。状態ハンドラー関数は次の状態を返します。必要に応じて、一部のイベントはメインイベントハンドラーで特別な処理を受けることができます。状態の開始と終了、およびおそらく状態マシンの起動のために擬似イベントをスローするのが好きです:

enum state_type { THIS, THAT, FOO, NA };
enum event_type { ET_START, ET_ENTER, ET_EXIT, ET_THIS, ET_THAT, ET_WHATEVER, ET_TIMEOUT };
union event_parm { uint8_t this; uint16_t that; };
static void handle_event(enum event_type event, union event_parm parm)
{
  static enum state_type state;
  static void (* const state_handler[])(enum event_type event, union event_parm parm) = { handle_this, handle_that };
  enum state_type next_state = state_handler[state](event, parm);
  if (NA != next_state && state != next_state)
  {
    (void)state_handler[state](ET_EXIT, 0);
    state = next_state;
    (void)state_handler[state](ET_ENTER, 0);
  }
}

特に関数ポインタの配列に関して、構文を使用したかどうかはわかりません。私はこれをコンパイラーで実行していません。確認すると、疑似イベント(state_handler()の呼び出し前の(void)括弧)を処理するときに、次の状態を明示的に破棄するのを忘れたことに気付きました。これは、コンパイラが省略を黙って受け入れたとしても、私がしたいことです。 「はい、戻り値を使用せずに関数を呼び出すつもりでした」とコードの読者に伝え、静的分析ツールが警告するのを止めることができます。私は他の誰かがこれをしているのを見たことを覚えていないので、それは特異であるかもしれません。

ポイント:少し複雑さを追加して(次の状態が現在のものと異なるかどうかを確認する)、他の場所でコードが重複しないようにします。状態ハンドラーの結果はこれらのイベントの後に破棄されるため、擬似イベントを処理するときに状態を変更することはできません。もちろん、振る舞いを変更することもできます。

状態ハンドラーは次のようになります。

static enum state_type handle_this(enum event_type event, union event_parm parm)
{
  enum state_type next_state = NA;
  switch (event)
  {
    case ET_ENTER:
    // Start a timer to do whatever.
    // Do other stuff necessary when entering this state.
    break;

    case ET_WHATEVER:
    // Switch state.
    next_state = THAT;
    break;

    case ET_TIMEOUT:
    // Switch state.
    next_state = FOO;
    break;

    case ET_EXIT:
    // Stop the timer.
    // Generally clean up this state.
    break;
  }
  return next_state;
}

より複雑な

コンパイル単位が大きくなりすぎた場合(つまり、1000行程度)、各状態ハンドラーを別々のファイルに入れます。各状態ハンドラーがいくつかの画面より長くなったら、状態スイッチが分割された方法と同様に、各イベントを個別の関数に分割します。これは、状態とは別に、または共通のテーブルを使用するか、さまざまなスキームを組み合わせることにより、さまざまな方法で実行できます。それらのいくつかは、他の人によってここでカバーされています。速度が必要な場合は、テーブルを並べ替えてバイナリ検索を使用します。

汎用プログラミング

プリプロセッサで、テーブルの並べ替えや説明からの状態マシンの生成などの問題を処理して、「プログラムに関するプログラムを作成」できるようにする必要があります。これはBoostの人々がC ++テンプレートを利用している理由だと思いますが、構文は不可解です。

2次元テーブル

過去に状態/イベントテーブルを使用しましたが、最も単純なケースでは必要ではないと私は言わなければなりません.1画面いっぱいになったとしても、switchステートメントの明快さと読みやすさを好みます。より複雑な場合、他の人が指摘したように、テーブルはすぐに手に負えなくなります。ここで紹介するイディオムを使用すると、メモリを消費するテーブルを(プログラムメモリであっても)維持しなくても、気になるときに多数のイベントと状態を追加できます。

免責事項

特別なニーズにより、これらのイディオムはあまり役に立たない場合がありますが、非常に明確で保守しやすいことがわかりました。

非常にテストされていませんが、コーディングは楽しいです。元の回答よりも洗練されたバージョンになりました。最新バージョンは mercurial.intuxicationにあります。組織

sm.h

#ifndef SM_ARGS
#error "SM_ARGS undefined: " \
    "use '#define SM_ARGS (void)' to get an empty argument list"
#endif

#ifndef SM_STATES
#error "SM_STATES undefined: " \
    "you must provide a list of comma-separated states"
#endif

typedef void (*sm_state) SM_ARGS;
static const sm_state SM_STATES;

#define sm_transit(STATE) ((sm_state (*) SM_ARGS)STATE)

#define sm_def(NAME) \
    static sm_state NAME ## _fn SM_ARGS; \
    static const sm_state NAME = (sm_state)NAME ## _fn; \
    static sm_state NAME ## _fn SM_ARGS

example.c

#include <stdio.h>

#define SM_ARGS (int i)
#define SM_STATES EVEN, ODD
#include "sm.h"

sm_def(EVEN)
{
    printf("even %i\n", i);
    return ODD;
}

sm_def(ODD)
{
    printf("odd  %i\n", i);
    return EVEN;
}

int main(void)
{
    int i = 0;
    sm_state state = EVEN;

    for(; i < 10; ++i)
        state = sm_transit(state)(i);

    return 0;
}

paxdiableの答えが本当に気に入ったので、ガード変数やステートマシン固有のデータなど、アプリケーションに不足しているすべての機能を実装することにしました。

実装をこのサイトにアップロードして、コミュニティと共有しました。 ARM用IAR Embedded Workbenchを使用してテストされています。

https://sourceforge.net/projects/compactfsm/

別の興味深いオープンソースツールは、 statecharts.orgのYakindu Statechart Tools です。 Harelステートチャートを利用して、階層的かつ並列的な状態を提供し、CおよびC ++(およびJava)コードを生成します。ライブラリは使用しませんが、「プレーンコード」アプローチに従います。コードは基本的にスイッチケース構造を適用します。コードジェネレーターもカスタマイズできます。さらに、このツールは他の多くの機能を提供します。

これまでに(いつものように)近づいてきましたが、今日までの答えをスキャンして、何か重要なものが欠けていると思います;

自分のプロジェクトで、有効なすべての状態/イベントの組み合わせに対して機能を持たないことが非常に役立つことがわかっています。状態/イベントの2Dテーブルを効果的に作成するというアイデアが好きです。しかし、テーブル要素は単純な関数ポインター以上のものであることが好きです。その代わりに、私は自分のデザインを整理しようと心がけています。そうすれば、状態/イベントテーブルの各交点にこれらの単純なアトミック要素をリストできます。アイデアは、N個の2乗(通常は非常に単純な)関数の質量を定義する必要がない ことです。なぜエラーが発生しやすく、時間がかかり、書きにくく、読みにくいものがあるのですか?

また、オプションの新しい状態、およびテーブル内の各セルのオプションの関数ポインターも含めます。関数ポインタは、アトミックアクションのリストだけを起動したくない例外的な場合に使用します。

テーブルを編集するだけで、新しいコードを記述することなく、多くの異なる機能を表現できるときに、あなたが正しいことをしていることを知っています。

Alrght、私のものは他の人とは少し違うと思う。他の回答で見たよりもコードとデータの分離が少し多くなっています。これを書くために理論を本当に読み上げました。これは完全な正規言語を実装しています(残念ながら正規表現はありません)。ウルマン、ミンスキー、チョムスキー。私はそれをすべて理解したとは言えませんが、私は古いマスターから可能な限り直接引き出しました:彼らの言葉を通して。

「yes」状態または「no」状態への遷移を決定する述語への関数ポインターを使用します。これにより、アセンブリ言語に似た方法でプログラムする通常言語用の有限状態アクセプターの作成が容易になります。 私の愚かな名前の選択に先送りされないでください。 'czek' == 'check'。 'grok' == [ハッカー辞書で調べてください。]

したがって、各反復で、czekは現在の文字を引数として述語関数を呼び出します。述部がtrueを返す場合、文字は消費され(ポインターが進み)、「y」遷移に従って次の状態を選択します。述語がfalseを返す場合、文字は消費されず、「n」遷移に従います。したがって、すべての命令は双方向ブランチです!私は当時メルの物語を読んでいたに違いありません。

このコードは、私のpostscriptインタープリターから直接取得し、多くのガイダンスで現在の形式に進化しました。 comp.lang.cのフェローから。 postscriptには基本的に構文がないため(バランスのとれた括弧のみが必要)、このような通常言語アクセプターはパーサーとしても機能します。

/* currentstr is set to the start of string by czek
   and used by setrad (called by israd) to set currentrad
   which is used by israddig to determine if the character
   in question is valid for the specified radix
   --
   a little semantic checking in the syntax!
 */
char *currentstr;
int currentrad;
void setrad(void) {
    char *end;
    currentrad = strtol(currentstr, &end, 10);
    if (*end != '#' /* just a sanity check,
                       the automaton should already have determined this */
    ||  currentrad > 36
    ||  currentrad < 2)
        fatal("bad radix"); /* should probably be a simple syntaxerror */
}

/*
   character classes
   used as tests by automatons under control of czek
 */
char *alpha = "0123456789" "ABCDE" "FGHIJ" "KLMNO" "PQRST" "UVWXYZ";
#define EQ(a,b) a==b
#define WITHIN(a,b) strchr(a,b)!=NULL
int israd  (int c) {
    if (EQ('#',c)) { setrad(); return true; }
    return false;
}
int israddig(int c) {
    return strchrnul(alpha,toupper(c))-alpha <= currentrad;
}
int isdot  (int c) {return EQ('.',c);}
int ise    (int c) {return WITHIN("eE",c);}
int issign (int c) {return WITHIN("+-",c);}
int isdel  (int c) {return WITHIN("()<>[]{}/%",c);}
int isreg  (int c) {return c!=EOF && !isspace(c) && !isdel(c);}
#undef WITHIN
#undef EQ

/*
   the automaton type
 */
typedef struct { int (*pred)(int); int y, n; } test;

/*
   automaton to match a simple decimal number
 */
/* /^[+-]?[0-9]+$/ */
test fsm_dec[] = {
/* 0*/ { issign,  1,  1 },
/* 1*/ { isdigit, 2, -1 },
/* 2*/ { isdigit, 2, -1 },
};
int acc_dec(int i) { return i==2; }

/*
   automaton to match a radix number
 */
/* /^[0-9]+[#][a-Z0-9]+$/ */
test fsm_rad[] = {
/* 0*/ { isdigit,  1, -1 },
/* 1*/ { isdigit,  1,  2 },
/* 2*/ { israd,    3, -1 },
/* 3*/ { israddig, 4, -1 },
/* 4*/ { israddig, 4, -1 },
};
int acc_rad(int i) { return i==4; }

/*
   automaton to match a real number
 */
/* /^[+-]?(d+(.d*)?)|(d*.d+)([eE][+-]?d+)?$/ */
/* represents the merge of these (simpler) expressions
   [+-]?[0-9]+\.[0-9]*([eE][+-]?[0-9]+)?
   [+-]?[0-9]*\.[0-9]+([eE][+-]?[0-9]+)?
   The complexity comes from ensuring at least one
   digit in the integer or the fraction with optional
   sign and optional optionally-signed exponent.
   So passing isdot in state 3 means at least one integer digit has been found
   but passing isdot in state 4 means we must find at least one fraction digit
   via state 5 or the whole thing is a bust.
 */
test fsm_real[] = {
/* 0*/ { issign,  1,   1 },
/* 1*/ { isdigit, 2,   4 },
/* 2*/ { isdigit, 2,   3 },
/* 3*/ { isdot,   6,   7 },
/* 4*/ { isdot,   5,  -1 },
/* 5*/ { isdigit, 6,  -1 },
/* 6*/ { isdigit, 6,   7 },
/* 7*/ { ise,     8,  -1 },
/* 8*/ { issign,  9,   9 },
/* 9*/ { isdigit, 10, -1 },
/*10*/ { isdigit, 10, -1 },
};
int acc_real(int i) {
    switch(i) {
        case 2: /* integer */
        case 6: /* real */
        case 10: /* real with exponent */
            return true;
    }
    return false;
}

/*
   Helper function for grok.
   Execute automaton against the buffer,
   applying test to each character:
       on success, consume character and follow 'y' transition.
       on failure, do not consume but follow 'n' transition.
   Call yes function to determine if the ending state
   is considered an acceptable final state.
   A transition to -1 represents rejection by the automaton
 */
int czek (char *s, test *fsm, int (*yes)(int)) {
    int sta = 0;
    currentstr = s;
    while (sta!=-1 && *s) {
        if (fsm[sta].pred((int)*s)) {
            sta=fsm[sta].y;
            s++;
        } else {
            sta=fsm[sta].n;
        }
    }
    return yes(sta);
}

/*
   Helper function for toke.
   Interpret the contents of the buffer,
   trying automatons to match number formats;
   and falling through to a switch for special characters.
   Any token consisting of all regular characters
   that cannot be interpreted as a number is an executable name
 */
object grok (state *st, char *s, int ns,
    object *src,
    int (*next)(state *,object *),
    void (*back)(state *,int, object *)) {

    if (czek(s, fsm_dec, acc_dec)) {
        long num;
        num = strtol(s,NULL,10);
        if ((num==LONG_MAX || num==LONG_MIN) && errno==ERANGE) {
            error(st,limitcheck);
/*       } else if (num > INT_MAX || num < INT_MIN) { */
/*           error(limitcheck, OP_token); */
        } else {
            return consint(num);
        }
    }

    else if (czek(s, fsm_rad, acc_rad)) {
        long ra,num;
        ra = (int)strtol(s,NULL,10);
        if (ra > 36 || ra < 2) {
            error(st,limitcheck);
        }
        num = strtol(strchr(s,'#')+1, NULL, (int)ra);
        if ((num==LONG_MAX || num==LONG_MIN) && errno==ERANGE) {
            error(st,limitcheck);
/*       } else if (num > INT_MAX || num < INT_MAX) { */
/*           error(limitcheck, OP_token); */
        } else {
            return consint(num);
        }
    }

    else if (czek(s, fsm_real, acc_real)) {
        double num;
        num = strtod(s,NULL);
        if ((num==HUGE_VAL || num==-HUGE_VAL) && errno==ERANGE) {
            error(st,limitcheck);
        } else {
            return consreal(num);
        }
    }

    else switch(*s) {
        case '(': {
            int c, defer=1;
            char *sp = s;

            while (defer && (c=next(st,src)) != EOF ) {
                switch(c) {
                    case '(': defer++; break;
                    case ')': defer--;
                        if (!defer) goto endstring;
                        break;
                    case '\\': c=next(st,src);
                        switch(c) {
                            case '\n': continue;
                            case 'a': c = '\a'; break;
                            case 'b': c = '\b'; break;
                            case 'f': c = '\f'; break;
                            case 'n': c = '\n'; break;
                            case 'r': c = '\r'; break;
                            case 't': c = '\t'; break;
                            case 'v': c = '\v'; break;
                            case '\'': case '\"':
                            case '(': case ')':
                            default: break;
                        }
                }
                if (sp-s>ns) error(st,limitcheck);
                else *sp++ = c;
            }
endstring:  *sp=0;
            return cvlit(consstring(st,s,sp-s));
        }

        case '<': {
            int c;
            char d, *x = "0123456789abcdef", *sp = s;
            while (c=next(st,src), c!='>' && c!=EOF) {
                if (isspace(c)) continue;
                if (isxdigit(c)) c = strchr(x,tolower(c)) - x;
                else error(st,syntaxerror);
                d = (char)c << 4;
                while (isspace(c=next(st,src))) /*loop*/;
                if (isxdigit(c)) c = strchr(x,tolower(c)) - x;
                else error(st,syntaxerror);
                d |= (char)c;
                if (sp-s>ns) error(st,limitcheck);
                *sp++ = d;
            }
            *sp = 0;
            return cvlit(consstring(st,s,sp-s));
        }

        case '{': {
            object *a;
            size_t na = 100;
            size_t i;
            object proc;
            object fin;

            fin = consname(st,"}");
            (a = malloc(na * sizeof(object))) || (fatal("failure to malloc"),0);
            for (i=0 ; objcmp(st,a[i]=toke(st,src,next,back),fin) != 0; i++) {
                if (i == na-1)
                (a = realloc(a, (na+=100) * sizeof(object))) || (fatal("failure to malloc"),0);
            }
            proc = consarray(st,i);
            { size_t j;
                for (j=0; j<i; j++) {
                    a_put(st, proc, j, a[j]);
                }
            }
            free(a);
            return proc;
        }

        case '/': {
            s[1] = (char)next(st,src);
            puff(st, s+2, ns-2, src, next, back);
            if (s[1] == '/') {
                push(consname(st,s+2));
                opexec(st, op_cuts.load);
                return pop();
            }
            return cvlit(consname(st,s+1));
        }

        default: return consname(st,s);
    }
    return null; /* should be unreachable */
}

/*
   Helper function for toke.
   Read into buffer any regular characters.
   If we read one too many characters, put it back
   unless it's whitespace.
 */
int puff (state *st, char *buf, int nbuf,
    object *src,
    int (*next)(state *,object *),
    void (*back)(state *,int, object *)) {
    int c;
    char *s = buf;
    while (isreg(c=next(st,src))) {
        if (s-buf >= nbuf-1) return false;
        *s++ = c;
    }
    *s = 0;
    if (!isspace(c) && c != EOF) back(st,c,src); /* eat interstice */
    return true;
}

/*
   Helper function for Stoken Ftoken.
   Read a token from src using next and back.
   Loop until having read a bona-fide non-whitespace non-comment character.
   Call puff to read into buffer up to next delimiter or space.
   Call grok to figure out what it is.
 */
#define NBUF MAXLINE
object toke (state *st, object *src,
        int (*next)(state *, object *),
        void (*back)(state *, int, object *)) {
    char buf[NBUF] = "", *s=buf;
    int c,sta = 1;
    object o;

    do {
        c=next(st,src);
        //if (c==EOF) return null;
        if (c=='%') {
            if (DUMPCOMMENTS) fputc(c, stdout);
            do {
                c=next(st,src);
                if (DUMPCOMMENTS) fputc(c, stdout);
            } while (c!='\n' && c!='\f' && c!=EOF);
        }
    } while (c!=EOF && isspace(c));
    if (c==EOF) return null;
    *s++ = c;
    *s = 0;
    if (!isdel(c)) sta=puff(st, s,NBUF-1,src,next,back);

    if (sta) {
        o=grok(st,buf,NBUF-1,src,next,back);
        return o;
    } else {
        return null;
    }
}

boost.orgには、2つの異なる状態図の実装が付属しています:

いつものように、boostはテンプレートヘルにビームを送ります。

最初のライブラリは、パフォーマンスが重要な状態マシン用です。 2番目のライブラリは、UMLステートチャートからコードへの直接遷移パスを提供します。

SOの質問で、2つの比較を求めています著者の回答。

このシリーズArs OpenForumの投稿には、制御ロジックのやや複雑な部分について、Cのステートマシンとして非常にわかりやすい実装が含まれています。

これをどこかで見た

#define FSM
#define STATE(x)      s_##x :
#define NEXTSTATE(x)  goto s_##x

FSM {
  STATE(x) {
    ...
    NEXTSTATE(y);
  }

  STATE(y) {
    ...
    if (x == 0)
      NEXTSTATE(y);
    else
      NEXTSTATE(x);
  }
}

C ++、したがってOOコードを使用できることを意味する場合、「GoF」ステートパターン(GoF = Gang of Four、デザインパターンを脚光を浴びたデザインパターンの本を書いた人)を評価することをお勧めします。

特に複雑ではなく、広く使用され、議論されているため、オンラインで例や説明を見やすくなっています。

また、後日あなたのコードを保守している他の誰もが認識できる可能性が高いでしょう。

効率が心配な場合は、多くの要因がパフォーマンスに影響を与えるため、OO以外のアプローチがより効率的であることを確認するために、実際にベンチマークする価値があります。同様に、メモリ使用量が制約である場合は、状態パターンを使用した場合に特定のアプリケーションで実際に問題になるかどうかを確認するために、いくつかのテストまたは計算を行う価値があります。

以下は、クレイグが示唆するように、「Gof」状態パターンへのリンクです。

あなたの質問は非常に一般的です、
役立つと思われる2つのリファレンス記事を以下に示します。

  1. 埋め込みステートマシンの実装

      

    この記事では、組み込みシステム用のステートマシンを実装する簡単な方法について説明します。この記事の目的上、ステートマシンは、少数の状態のいずれかになりうるアルゴリズムとして定義されています。状態とは、入力と出力、および入力と次の状態の所定の関係を引き起こす条件です。
      この記事で説明するステートマシンはMealyマシンであることに、読者はすぐに気付くでしょう。 Mealyマシンは、出力が状態のみの関数であるムーアマシンとは対照的に、出力が現在の状態と入力の両方の関数である状態マシンです。

    • CおよびC ++でのステートマシンのコーディング
        

      この記事での私の関心は、ステートマシンの基礎と、CまたはC ++でステートマシンをコーディングするための簡単なプログラミングガイドラインです。これらのシンプルなテクニックがより一般的になり、あなた(および他の人)がソースコードからステートマシン構造をすぐに見ることができることを願っています。

JavaおよびPythonプロジェクトで State Machine Compiler を使用して成功しました。

これは多くの答えがある古い投稿ですが、Cの有限状態マシンに独自のアプローチを追加すると思いました。任意の数の状態のスケルトンCコードを生成するPythonスクリプトを作成しました。このスクリプトは、GituHubの FsmTemplateC

に記載されています。

この例は、私が読んだ他のアプローチに基づいています。 gotoまたはswitchステートメントは使用しませんが、代わりにポインターマトリックス(ルックアップテーブル)に遷移関数があります。このコードは、大きな複数行イニシャライザーマクロとC99機能(指定されたイニシャライザーと複合リテラル)に依存しているため、これらのものが気に入らない場合は、このアプローチが気に入らないかもしれません。

これは、ターンスタイルの例のPythonスクリプトです FsmTemplateC を使用してスケルトンCコードを生成します:

# dict parameter for generating FSM
fsm_param = {
    # main FSM struct type string
    'type': 'FsmTurnstile',
    # struct type and name for passing data to state machine functions
    # by pointer (these custom names are optional)
    'fopts': {
        'type': 'FsmTurnstileFopts',
        'name': 'fopts'
    },
    # list of states
    'states': ['locked', 'unlocked'],
    # list of inputs (can be any length > 0)
    'inputs': ['coin', 'push'],
    # map inputs to commands (next desired state) using a transition table
    # index of array corresponds to 'inputs' array
    # for this example, index 0 is 'coin', index 1 is 'push'
    'transitiontable': {
        # current state |  'coin'  |  'push'  |
        'locked':       ['unlocked',        ''],
        'unlocked':     [        '',  'locked']
    }
}

# folder to contain generated code
folder = 'turnstile_example'
# function prefix
prefix = 'fsm_turnstile'

# generate FSM code
code = fsm.Fsm(fsm_param).genccode(folder, prefix)

生成された出力ヘッダーにはtypedefが含まれています:

/* function options (EDIT) */
typedef struct FsmTurnstileFopts {
    /* define your options struct here */
} FsmTurnstileFopts;

/* transition check */
typedef enum eFsmTurnstileCheck {
    EFSM_TURNSTILE_TR_RETREAT,
    EFSM_TURNSTILE_TR_ADVANCE,
    EFSM_TURNSTILE_TR_CONTINUE,
    EFSM_TURNSTILE_TR_BADINPUT
} eFsmTurnstileCheck;

/* states (enum) */
typedef enum eFsmTurnstileState {
    EFSM_TURNSTILE_ST_LOCKED,
    EFSM_TURNSTILE_ST_UNLOCKED,
    EFSM_TURNSTILE_NUM_STATES
} eFsmTurnstileState;

/* inputs (enum) */
typedef enum eFsmTurnstileInput {
    EFSM_TURNSTILE_IN_COIN,
    EFSM_TURNSTILE_IN_PUSH,
    EFSM_TURNSTILE_NUM_INPUTS,
    EFSM_TURNSTILE_NOINPUT
} eFsmTurnstileInput;

/* finite state machine struct */
typedef struct FsmTurnstile {
    eFsmTurnstileInput input;
    eFsmTurnstileCheck check;
    eFsmTurnstileState cur;
    eFsmTurnstileState cmd;
    eFsmTurnstileState **transition_table;
    void (***state_transitions)(struct FsmTurnstile *, FsmTurnstileFopts *);
    void (*run)(struct FsmTurnstile *, FsmTurnstileFopts *, const eFsmTurnstileInput);
} FsmTurnstile;

/* transition functions */
typedef void (*pFsmTurnstileStateTransitions)(struct FsmTurnstile *, FsmTurnstileFopts *);
  • enum eFsmTurnstileCheck は、 EFSM_TURNSTILE_TR_RETREAT で遷移がブロックされたか、 EFSM_TURNSTILE_TR_ADVANCE で進行を許可されたか、または関数呼び出しがされなかったかを判断するために使用されます EFSM_TURNSTILE_TR_CONTINUE による遷移が先行します。
  • enum eFsmTurnstileState は単なる状態のリストです。
  • enum eFsmTurnstileInput は単に入力のリストです。
  • FsmTurnstile 構造体は、遷移チェック、関数ルックアップテーブル、現在の状態、コマンドされた状態、およびマシンを実行するプライマリ関数のエイリアスを備えたステートマシンの中心です。
  • FsmTurnstile 内のすべての関数ポインター(エイリアス)は、構造体からのみ呼び出す必要があり、オブジェクト指向型の永続的な状態を維持するために、自身へのポインターとして最初の入力が必要です。

ヘッダー内の関数宣言の説明:

/* fsm declarations */
void fsm_turnstile_locked_locked (FsmTurnstile *fsm, FsmTurnstileFopts *fopts);
void fsm_turnstile_locked_unlocked (FsmTurnstile *fsm, FsmTurnstileFopts *fopts);
void fsm_turnstile_unlocked_locked (FsmTurnstile *fsm, FsmTurnstileFopts *fopts);
void fsm_turnstile_unlocked_unlocked (FsmTurnstile *fsm, FsmTurnstileFopts *fopts);
void fsm_turnstile_run (FsmTurnstile *fsm, FsmTurnstileFopts *fopts, const eFsmTurnstileInput input);

関数名の形式は {prefix} _ {from} _ {to} です。ここで、 {from} は前の(現在の)状態で、 {to} は次の状態です。遷移テーブルが特定の遷移を許可しない場合、関数ポインターの代わりにNULLポインターが設定されることに注意してください。最後に、魔法はマクロで起こります。ここでは、遷移テーブル(状態列挙のマトリックス)と状態遷移関数ルックアップテーブル(関数ポインターのマトリックス)を作成します。

/* creation macro */
#define FSM_TURNSTILE_CREATE() \
{ \
    .input = EFSM_TURNSTILE_NOINPUT, \
    .check = EFSM_TURNSTILE_TR_CONTINUE, \
    .cur = EFSM_TURNSTILE_ST_LOCKED, \
    .cmd = EFSM_TURNSTILE_ST_LOCKED, \
    .transition_table = (eFsmTurnstileState * [EFSM_TURNSTILE_NUM_STATES]) { \
        (eFsmTurnstileState [EFSM_TURNSTILE_NUM_INPUTS]) { \
            EFSM_TURNSTILE_ST_UNLOCKED, \
            EFSM_TURNSTILE_ST_LOCKED \
        }, \
        (eFsmTurnstileState [EFSM_TURNSTILE_NUM_INPUTS]) { \
            EFSM_TURNSTILE_ST_UNLOCKED, \
            EFSM_TURNSTILE_ST_LOCKED \
        } \
    }, \
    .state_transitions = (pFsmTurnstileStateTransitions * [EFSM_TURNSTILE_NUM_STATES]) { \
        (pFsmTurnstileStateTransitions [EFSM_TURNSTILE_NUM_STATES]) { \
            fsm_turnstile_locked_locked, \
            fsm_turnstile_locked_unlocked \
        }, \
        (pFsmTurnstileStateTransitions [EFSM_TURNSTILE_NUM_STATES]) { \
            fsm_turnstile_unlocked_locked, \
            fsm_turnstile_unlocked_unlocked \
        } \
    }, \
    .run = fsm_turnstile_run \
}

FSMを作成するときは、マクロ FSM_EXAMPLE_CREATE()を使用する必要があります。

今、ソースコードでは、上記で宣言されたすべての状態遷移関数を設定する必要があります。 FsmTurnstileFopts 構造体を使用して、ステートマシンとの間でデータをやり取りできます。すべての遷移は、 fsm-&gt; check を、遷移をブロックする EFSM_EXAMPLE_TR_RETREAT か、 EFSM_EXAMPLE_TR_ADVANCE のいずれかに等しくなるように設定する必要があります命令された状態。 実際の例は、(FsmTemplateC)[ https://github.com/ChisholmKyle/FsmTemplateC]。

コードでの実際の使用法は非常に単純です:

/* create fsm */
FsmTurnstile fsm = FSM_TURNSTILE_CREATE();
/* create fopts */
FsmTurnstileFopts fopts = {
    .msg = ""
};
/* initialize input */
eFsmTurnstileInput input = EFSM_TURNSTILE_NOINPUT;

/* main loop */
for (;;) {
    /* wait for timer signal, inputs, interrupts, whatever */
    /* optionally set the input (my_input = EFSM_TURNSTILE_IN_PUSH for example) */
    /* run state machine */
    my_fsm.run(&my_fsm, &my_fopts, my_input);
}

すべてのヘッダービジネスと、シンプルで高速なインターフェイスを実現するためのすべての機能は、私にとっては価値があります。

オープンソースライブラリ OpenFST を使用できます。

  

OpenFstは、重み付き有限状態トランスデューサ(FST)を構築、結合、最適化、および検索するためのライブラリです。重み付き有限状態トランスデューサは、各遷移に入力ラベル、出力ラベル、および重みがあるオートマトンです。より馴染みのある有限状態アクセプターは、各遷移の入力ラベルと出力ラベルが等しいトランスデューサーとして表されます。有限状態アクセプターは、文字列のセット(具体的には、通常のセットまたは合理的なセット)を表すために使用されます。有限状態トランスデューサは、文字列のペア間のバイナリ関係(具体的には、合理的な変換)を表すために使用されます。重みは、特定の遷移を行うコストを表すために使用できます。

void (* StateController)(void); 
void state1(void);
void state2(void);

void main()
{
 StateController=&state1;
 while(1)
 {
  (* StateController)();
 }
}

void state1(void)
{
 //do something in state1
 StateController=&state2;
}

void state2(void)
{
 //do something in state2
 //Keep changing function direction based on state transition
 StateController=&state1;
}

私が個人的に利用した自己参照構造体と組み合わせポインタ配列.いろいろ考えさせら、ストリートビューをgithub械的風味さん一応前に線路、リンク:

https://github.com/mmelchger/polling_state_machine_c

注意:いうこのスレッドが古く、私の希望する入力について考えるデザインの状態-機械などでは、例としての可能性につながる状態-機械デザインC

これは、イベントとしてメッセージキューを使用するLinux用の有限状態マシンの例です。イベントはキューに入れられ、順番に処理されます。状態は、各イベントの処理内容に応じて変わります。

これは、次のような状態のデータ接続の例です。

  • 未初期化
  • 初期化
  • 接続済み
  • MTU交渉済み
  • 認証済み

追加した1つの小さな追加機能は、各メッセージ/イベントのタイムスタンプでした。イベントハンドラーは、古すぎる(期限切れの)イベントを無視します。これは、予想外の状態に陥る可能性がある現実の世界で多く発生する可能性があります。

この例はLinux上で実行されます。以下のMakefileを使用してコンパイルし、試してください。

state_machine.c

#include <stdio.h>
#include <stdint.h>
#include <assert.h>
#include <unistd.h>   // sysconf()
#include <errno.h>    // errno
#include <string.h>   // strerror()
#include <sys/time.h> // gettimeofday()
#include <fcntl.h>    // For O_* constants
#include <sys/stat.h> // For mode constants

#include <mqueue.h>
#include <poll.h>

//------------------------------------------------
// States
//------------------------------------------------
typedef enum
{
    ST_UNKNOWN = 0,
    ST_UNINIT,
    ST_INIT,
    ST_CONNECTED,
    ST_MTU_NEGOTIATED,
    ST_AUTHENTICATED,
    ST_ERROR,
    ST_DONT_CHANGE,
    ST_TERM,
} fsmState_t;

//------------------------------------------------
// Events
//------------------------------------------------
typedef enum
{
    EV_UNKNOWN = 0,
    EV_INIT_SUCCESS,
    EV_INIT_FAIL,
    EV_MASTER_CMD_MSG,
    EV_CONNECT_SUCCESS,
    EV_CONNECT_FAIL,
    EV_MTU_SUCCESS,
    EV_MTU_FAIL,
    EV_AUTH_SUCCESS,
    EV_AUTH_FAIL,
    EV_TX_SUCCESS,
    EV_TX_FAIL,
    EV_DISCONNECTED,
    EV_DISCON_FAILED,
    EV_LAST_ENTRY,
} fsmEvName_t;

typedef struct fsmEvent_type
{
    fsmEvName_t name;
    struct timeval genTime; // Time the event was generated.
                            // This allows us to see how old the event is.
} fsmEvent_t;

// Finite State Machine Data Members
typedef struct fsmData_type
{
    int  connectTries;
    int  MTUtries;
    int  authTries;
    int  txTries;
} fsmData_t;

// Each row of the state table
typedef struct stateTable_type {
    fsmState_t  st;             // Current state
    fsmEvName_t evName;         // Got this event
    int (*conditionfn)(void *);  // If this condition func returns TRUE
    fsmState_t nextState;       // Change to this state and
    void (*fn)(void *);          // Run this function
} stateTable_t;

// Finite State Machine state structure
typedef struct fsm_type
{
    const stateTable_t *pStateTable; // Pointer to state table
    int        numStates;            // Number of entries in the table
    fsmState_t currentState;         // Current state
    fsmEvent_t currentEvent;         // Current event
    fsmData_t *fsmData;              // Pointer to the data attributes
    mqd_t      mqdes;                // Message Queue descriptor
    mqd_t      master_cmd_mqdes;     // Master command message queue
} fsm_t;

// Wildcard events and wildcard state
#define   EV_ANY    -1
#define   ST_ANY    -1
#define   TRUE     (1)
#define   FALSE    (0)

// Maximum priority for message queues (see "man mq_overview")
#define FSM_PRIO  (sysconf(_SC_MQ_PRIO_MAX) - 1)

static void addev                              (fsm_t *fsm, fsmEvName_t ev);
static void doNothing                          (void *fsm) {addev(fsm, EV_MASTER_CMD_MSG);}
static void doInit                             (void *fsm) {addev(fsm, EV_INIT_SUCCESS);}
static void doConnect                          (void *fsm) {addev(fsm, EV_CONNECT_SUCCESS);}
static void doMTU                              (void *fsm) {addev(fsm, EV_MTU_SUCCESS);}
static void reportFailConnect                  (void *fsm) {addev(fsm, EV_ANY);}
static void doAuth                             (void *fsm) {addev(fsm, EV_AUTH_SUCCESS);}
static void reportDisConnect                   (void *fsm) {addev(fsm, EV_ANY);}
static void doDisconnect                       (void *fsm) {addev(fsm, EV_ANY);}
static void doTransaction                      (void *fsm) {addev(fsm, EV_TX_FAIL);}
static void fsmError                           (void *fsm) {addev(fsm, EV_ANY);}

static int currentlyLessThanMaxConnectTries    (void *fsm) {
    fsm_t *l = (fsm_t *)fsm;
    return (l->fsmData->connectTries < 5 ? TRUE : FALSE);
}
static int        isMoreThanMaxConnectTries    (void *fsm) {return TRUE;}
static int currentlyLessThanMaxMTUtries        (void *fsm) {return TRUE;}
static int        isMoreThanMaxMTUtries        (void *fsm) {return TRUE;}
static int currentyLessThanMaxAuthTries        (void *fsm) {return TRUE;}
static int       isMoreThanMaxAuthTries        (void *fsm) {return TRUE;}
static int currentlyLessThanMaxTXtries         (void *fsm) {return FALSE;}
static int        isMoreThanMaxTXtries         (void *fsm) {return TRUE;}
static int didNotSelfDisconnect                (void *fsm) {return TRUE;}

static int  waitForEvent                       (fsm_t *fsm);
static void runEvent                           (fsm_t *fsm);
static void runStateMachine(fsm_t *fsm);
static int newEventIsValid(fsmEvent_t *event);
static void getTime(struct timeval *time);
void printState(fsmState_t st);
void printEvent(fsmEvName_t ev);

// Global State Table
const stateTable_t GST[] = {
    // Current state         Got this event          If this condition func returns TRUE     Change to this state and    Run this function
    { ST_UNINIT,             EV_INIT_SUCCESS,        NULL,                                   ST_INIT,                    &doNothing              },
    { ST_UNINIT,             EV_INIT_FAIL,           NULL,                                   ST_UNINIT,                  &doInit                 },
    { ST_INIT,               EV_MASTER_CMD_MSG,      NULL,                                   ST_INIT,                    &doConnect              },
    { ST_INIT,               EV_CONNECT_SUCCESS,     NULL,                                   ST_CONNECTED,               &doMTU                  },
    { ST_INIT,               EV_CONNECT_FAIL,        &currentlyLessThanMaxConnectTries,      ST_INIT,                    &doConnect              },
    { ST_INIT,               EV_CONNECT_FAIL,        &isMoreThanMaxConnectTries,             ST_INIT,                    &reportFailConnect      },
    { ST_CONNECTED,          EV_MTU_SUCCESS,         NULL,                                   ST_MTU_NEGOTIATED,          &doAuth                 },
    { ST_CONNECTED,          EV_MTU_FAIL,            &currentlyLessThanMaxMTUtries,          ST_CONNECTED,               &doMTU                  },
    { ST_CONNECTED,          EV_MTU_FAIL,            &isMoreThanMaxMTUtries,                 ST_CONNECTED,               &doDisconnect           },
    { ST_CONNECTED,          EV_DISCONNECTED,        &didNotSelfDisconnect,                  ST_INIT,                    &reportDisConnect       },
    { ST_MTU_NEGOTIATED,     EV_AUTH_SUCCESS,        NULL,                                   ST_AUTHENTICATED,           &doTransaction          },
    { ST_MTU_NEGOTIATED,     EV_AUTH_FAIL,           &currentyLessThanMaxAuthTries,          ST_MTU_NEGOTIATED,          &doAuth                 },
    { ST_MTU_NEGOTIATED,     EV_AUTH_FAIL,           &isMoreThanMaxAuthTries,                ST_MTU_NEGOTIATED,          &doDisconnect           },
    { ST_MTU_NEGOTIATED,     EV_DISCONNECTED,        &didNotSelfDisconnect,                  ST_INIT,                    &reportDisConnect       },
    { ST_AUTHENTICATED,      EV_TX_SUCCESS,          NULL,                                   ST_AUTHENTICATED,           &doDisconnect           },
    { ST_AUTHENTICATED,      EV_TX_FAIL,             &currentlyLessThanMaxTXtries,           ST_AUTHENTICATED,           &doTransaction          },
    { ST_AUTHENTICATED,      EV_TX_FAIL,             &isMoreThanMaxTXtries,                  ST_AUTHENTICATED,           &doDisconnect           },
    { ST_AUTHENTICATED,      EV_DISCONNECTED,        &didNotSelfDisconnect,                  ST_INIT,                    &reportDisConnect       },
    { ST_ANY,                EV_DISCON_FAILED,       NULL,                                   ST_DONT_CHANGE,             &doDisconnect           },
    { ST_ANY,                EV_ANY,                 NULL,                                   ST_UNINIT,                  &fsmError               }    // Wildcard state for errors
};

#define GST_COUNT (sizeof(GST)/sizeof(stateTable_t))

int main()
{
    int ret = 0;
    fsmData_t dataAttr;
    dataAttr.connectTries = 0;
    dataAttr.MTUtries     = 0;
    dataAttr.authTries    = 0;
    dataAttr.txTries      = 0;

    fsm_t lfsm;
    memset(&lfsm, 0, sizeof(fsm_t));
    lfsm.pStateTable       = GST;
    lfsm.numStates         = GST_COUNT;
    lfsm.currentState      = ST_UNINIT;
    lfsm.currentEvent.name = EV_ANY;
    lfsm.fsmData           = &dataAttr;

    struct mq_attr attr;
    attr.mq_maxmsg = 30;
    attr.mq_msgsize = sizeof(fsmEvent_t);

    // Dev info
    //printf("Size of fsmEvent_t [%ld]\n", sizeof(fsmEvent_t));

    ret = mq_unlink("/abcmq");
    if (ret == -1) {
        fprintf(stderr, "Error on mq_unlink(), errno[%d] strerror[%s]\n",
                errno, strerror(errno));
    }

    lfsm.mqdes = mq_open("/abcmq", O_CREAT | O_RDWR, S_IWUSR | S_IRUSR, &attr);
    if (lfsm.mqdes == (mqd_t)-1) {
        fprintf(stderr, "Error on mq_open(), errno[%d] strerror[%s]\n",
                errno, strerror(errno));
        return -1;
    }

    doInit(&lfsm);  // This will generate the first event
    runStateMachine(&lfsm);

    return 0;
}


static void runStateMachine(fsm_t *fsm)
{
    int ret = 0;

    if (fsm == NULL) {
        fprintf(stderr, "[%s] NULL argument\n", __func__);
        return;
    }

    // Cycle through the state machine
    while (fsm->currentState != ST_TERM) {
        printf("current state [");
        printState(fsm->currentState);
        printf("]\n");

        ret = waitForEvent(fsm);
        if (ret == 0) {
            printf("got event [");
            printEvent(fsm->currentEvent.name);
            printf("]\n");

            runEvent(fsm);
        }
        sleep(2);
    }
}


static int waitForEvent(fsm_t *fsm)
{
    //const int numFds = 2;
    const int numFds = 1;
    struct pollfd fds[numFds];
    int timeout_msecs = -1; // -1 is forever
    int ret = 0;
    int i = 0;
    ssize_t num = 0;
    fsmEvent_t newEv;

    if (fsm == NULL) {
        fprintf(stderr, "[%s] NULL argument\n", __func__);
        return -1;
    }

    fsm->currentEvent.name = EV_ANY;

    fds[0].fd     = fsm->mqdes;
    fds[0].events = POLLIN;
    //fds[1].fd     = fsm->master_cmd_mqdes;
    //fds[1].events = POLLIN;
    ret = poll(fds, numFds, timeout_msecs);

    if (ret > 0) {
        // An event on one of the fds has occurred
        for (i = 0; i < numFds; i++) {
            if (fds[i].revents & POLLIN) {
                // Data may be read on device number i
                num = mq_receive(fds[i].fd, (void *)(&newEv),
                                 sizeof(fsmEvent_t), NULL);
                if (num == -1) {
                    fprintf(stderr, "Error on mq_receive(), errno[%d] "
                            "strerror[%s]\n", errno, strerror(errno));
                    return -1;
                }

                if (newEventIsValid(&newEv)) {
                    fsm->currentEvent = newEv;
                } else {
                    return -1;
                }
            }
        }
    } else {
        fprintf(stderr, "Error on poll(), ret[%d] errno[%d] strerror[%s]\n",
                ret, errno, strerror(errno));
        return -1;
    }

    return 0;
}


static int newEventIsValid(fsmEvent_t *event)
{
    if (event == NULL) {
        fprintf(stderr, "[%s] NULL argument\n", __func__);
        return FALSE;
    }

    printf("[%s]\n", __func__);

    struct timeval now;
    getTime(&now);

    if ( (event->name < EV_LAST_ENTRY) &&
         ((now.tv_sec - event->genTime.tv_sec) < (60*5))
       )
    {
        return TRUE;
    } else {
        return FALSE;
    }
}


//------------------------------------------------
// Performs event handling on the FSM (finite state machine).
// Make sure there is a wildcard state at the end of
// your table, otherwise; the event will be ignored.
//------------------------------------------------
static void runEvent(fsm_t *fsm)
{
    int i;
    int condRet = 0;

    if (fsm == NULL) {
        fprintf(stderr, "[%s] NULL argument\n", __func__);
        return;
    }

    printf("[%s]\n", __func__);

    // Find a relevant entry for this state and event
    for (i = 0; i < fsm->numStates; i++) {
        // Look in the table for our current state or ST_ANY
        if (  (fsm->pStateTable[i].st == fsm->currentState) ||
              (fsm->pStateTable[i].st == ST_ANY)
           )
        {
            // Is this the event we are looking for?
            if ( (fsm->pStateTable[i].evName == fsm->currentEvent.name) ||
                 (fsm->pStateTable[i].evName == EV_ANY)
               )
            {
                if (fsm->pStateTable[i].conditionfn != NULL) {
                    condRet = fsm->pStateTable[i].conditionfn(fsm->fsmData);
                }

                // See if there is a condition associated
                // or we are not looking for any condition
                //
                if ( (condRet != 0) || (fsm->pStateTable[i].conditionfn == NULL))
                {
                    // Set the next state (if applicable)
                    if (fsm->pStateTable[i].nextState != ST_DONT_CHANGE) {
                        fsm->currentState = fsm->pStateTable[i].nextState;
                        printf("new state [");
                        printState(fsm->currentState);
                        printf("]\n");
                    }

                    // Call the state callback function
                    fsm->pStateTable[i].fn(fsm);
                    break;
                }
            }
        }
    }
}


//------------------------------------------------
//               EVENT HANDLERS
//------------------------------------------------
static void getTime(struct timeval *time)
{
    if (time == NULL) {
        fprintf(stderr, "[%s] NULL argument\n", __func__);
        return;
    }

    printf("[%s]\n", __func__);

    int ret = gettimeofday(time, NULL);
    if (ret != 0) {
        fprintf(stderr, "gettimeofday() failed: errno [%d], strerror [%s]\n",
                errno, strerror(errno));
        memset(time, 0, sizeof(struct timeval));
    }
}


static void addev (fsm_t *fsm, fsmEvName_t ev)
{
    int ret = 0;

    if (fsm == NULL) {
        fprintf(stderr, "[%s] NULL argument\n", __func__);
        return;
    }

    printf("[%s] ev[%d]\n", __func__, ev);

    if (ev == EV_ANY) {
        // Don't generate a new event, just return...
        return;
    }

    fsmEvent_t newev;
    getTime(&(newev.genTime));
    newev.name = ev;

    ret = mq_send(fsm->mqdes, (void *)(&newev), sizeof(fsmEvent_t), FSM_PRIO);
    if (ret == -1) {
        fprintf(stderr, "[%s] mq_send() failed: errno [%d], strerror [%s]\n",
                __func__, errno, strerror(errno));
    }
}
//------------------------------------------------
//           end EVENT HANDLERS
//------------------------------------------------

void printState(fsmState_t st)
{
    switch(st) {
        case    ST_UNKNOWN:
        printf("ST_UNKNOWN");
            break;
        case    ST_UNINIT:
        printf("ST_UNINIT");
            break;
        case    ST_INIT:
        printf("ST_INIT");
            break;
        case    ST_CONNECTED:
        printf("ST_CONNECTED");
            break;
        case    ST_MTU_NEGOTIATED:
        printf("ST_MTU_NEGOTIATED");
            break;
        case    ST_AUTHENTICATED:
        printf("ST_AUTHENTICATED");
            break;
        case    ST_ERROR:
        printf("ST_ERROR");
            break;
        case    ST_TERM:
        printf("ST_TERM");
            break;
        default:
        printf("unknown state");
            break;
    }
}

void printEvent(fsmEvName_t ev)
{
    switch (ev) {
        case    EV_UNKNOWN:
        printf("EV_UNKNOWN");
            break;
        case    EV_INIT_SUCCESS:
        printf("EV_INIT_SUCCESS");
            break;
        case    EV_INIT_FAIL:
        printf("EV_INIT_FAIL");
            break;
        case    EV_MASTER_CMD_MSG:
        printf("EV_MASTER_CMD_MSG");
            break;
        case    EV_CONNECT_SUCCESS:
        printf("EV_CONNECT_SUCCESS");
            break;
        case    EV_CONNECT_FAIL:
        printf("EV_CONNECT_FAIL");
            break;
        case    EV_MTU_SUCCESS:
        printf("EV_MTU_SUCCESS");
            break;
        case    EV_MTU_FAIL:
        printf("EV_MTU_FAIL");
            break;
        case    EV_AUTH_SUCCESS:
        printf("EV_AUTH_SUCCESS");
            break;
        case    EV_AUTH_FAIL:
        printf("EV_AUTH_FAIL");
            break;
        case    EV_TX_SUCCESS:
        printf("EV_TX_SUCCESS");
            break;
        case    EV_TX_FAIL:
        printf("EV_TX_FAIL");
            break;
        case    EV_DISCONNECTED:
        printf("EV_DISCONNECTED");
            break;
        case    EV_LAST_ENTRY:
        printf("EV_LAST_ENTRY");
            break;
        default:
        printf("unknown event");
            break;
    }
}

メークファイル

CXX = gcc
COMPFLAGS = -c -Wall -g

state_machine: state_machine.o
    $(CXX) -lrt state_machine.o -o state_machine

state_machine.o: state_machine.c
    $(CXX) $(COMPFLAGS) state_machine.c

clean:
    rm state_machine state_machine.o
ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top