Cステートマシン設計[終了]


193

CとC ++が混在する小さなプロジェクトを作成しています。私は、ワーカースレッドの1つの中心に、小さな風のステートマシンを1つ構築しています。

SOの達人があなたのステートマシン設計テクニックを共有するかどうか疑問に思っていました。

注:私は主に、試行錯誤した実装技術の後にいます。

更新: SOで収集されたすべての優れた入力に基づいて、私はこのアーキテクチャで解決しました:

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.


4
ここでの答えはとても良いです。:また、あまりにも、いくつかの良い答えを持って、この重複質問を見てstackoverflow.com/questions/1371460/state-machines-tutorials
マイケル・バリ

2
また、これは興味深いです:stackoverflow.com/questions/133214/...
ダニエルDaranas


こちらもご覧ください この質問
ダニエルダラナス

回答:


170

以前に設計したステートマシン(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回限り、プロセス開始時、構成ファイルの読み取り)であり、複数のインスタンスを実行する必要がないためです。 。しかし、複数を実行する必要がある場合は、価値があります。


24
巨大なスイッチがFSMとコードを混ぜ合わせます。トランジションごとに関数呼び出ししかない場合でもコードは残ります。小さな4行のトランジションをインラインに追加するだけで、悪用されるのは簡単です。ヘンテンライン。その後、手に負えなくなります。構造体配列を使用すると、FSMはクリーンなままです。すべての遷移と効果(関数)を確認できます。列挙型がいたコンパイラで6809の組み込みプラットフォーム用のコードを書いて、ISOの目できらめきだったとき、私たちはあまり:-)完璧よりも、と言うもの、開始
paxdiablo

5
ええ、そうです、列挙型のほうがいいですが、私はFSMを構造体配列として持つことを好みます。その後、すべてコードではなくデータによって実行されます(まあ、いくつかのコードがありますが、私が与えたFSMループを詰め込む可能性はわずかです)。
paxdiablo 2009年

2
これは、私が常にすべての状態に3つの(空の可能性がある)サブ状態を追加するために使用したプロセス制御状態マシンに適しているため、状態関数の呼び出しはGotKey(サブ状態)となり、サブ状態は次のようになります。-SS_ENTRY-SS_RUN-SS_EXIT基本的に、状態関数はエントリー時にSS_ENTRYサブ状態で呼び出されるため、状態は状態(例えば、アクチュエーターの位置)を再構築できます。遷移はありませんが、SS_RUNサブステート値が渡されます。遷移時に、状態関数はSS_EXITサブ状態で呼び出され、クリーンアップ(リソースの割り当て解除など)を実行できます。
Metiu、2009年

13
グローバルを使用してデータを共有すると述べましたが、各状態関数がパラメーターとして受け取るデータへのポインターがint (*fn)(void*);どこでvoid*あるかを状態関数に定義すると、おそらくよりクリーンになります。次に、状態関数はデータを使用するか、無視します。
ldog

13
私はFSMの作成に同じデータ/コード分離を使用していますが、「ワイルドカード」状態を導入することは思いもよらないことでした。面白いアイデア!ただし、状態の数が多い場合は、遷移の配列の反復処理に負荷がかかる可能性があります(Cコードが自動的に生成されたため、これは私にとってそうでした)。このような状況では、状態ごとに1つの配列の遷移を作成する方が効率的です。したがって、状態は列挙値ではなく、遷移テーブルです。そうすれば、マシンのすべての遷移を繰り返す必要はなく、現在の状態に関連する遷移だけを繰り返すことができます。
Frerich Raabe、2011

78

他の答えは良いですが、状態マシンが非常に単純なときに使用した非常に「軽量」な実装は次のようになります。

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 ... */
    }
}

これは、ステートマシンが非常に単純で、関数ポインターと状態遷移テーブルのアプローチが過剰な場合に使用します。これは多くの場合、文字単位または単語単位の解析に役立ちます。


37

コンピューターサイエンスのすべての規則に違反していることを許してください。ただし、ステートマシンは、gotoステートメントが効率的であるだけでなく、コードがより簡潔で読みやすくなる数少ない(私は2つしか数えられません)場所の1つです。なぜならgotoステートメントはラベルに基づいている、数字の混乱を追跡したり、列挙型を使用したりする代わりに、状態に名前を付けることができます。また、関数ポインターや巨大な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ステートメントを使用している場合でも、足元で自分を撃つことは非常に簡単なので、注意する必要があります。


4
これは、ステートマシンが最上位オブジェクトにある場合にのみ機能します。ときどきポーリングまたはメッセージを送信する他のオブジェクトが状態を持つ必要がある瞬間、このアプローチで立ち往生する(または、それをはるかに複雑にする必要がある)
skrebbel

1
これにより、最も単純な場合を除いて、プリエンプティブマルチタスクを使用することが強制されます。
Craig McQueen

1
これらのgotoは、関数呼び出しで置き換えることができます。また、プロファイラーから、関数呼び出しのオーバーヘッドが原因でプログラムが溺死していると通知された場合は、必要に応じて呼び出しをgotosに置き換えることができます。
Abtin Forouzandeh 2012

7
@AbtinForouzandehは、単にgotoを関数呼び出しに置き換えるだけで、エラーの場合にのみ呼び出しスタックがクリアされるため、stackoverflowが発生します。
JustMaximumPower

gotoメソッドに同意します。これを説明する一連のマクロを次に示します。マクロは、通常のようにコーディングしたかのようにコードを構造化します。また、ステート・マシンが必要とされている場合、通常は割り込みレベルで動作しますcodeproject.com/Articles/37037/...を
eddyq

30

ステートマシンコンパイラhttp://smc.sourceforge.net/

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

これを行う理由は、GoF状態パターンまたはその他のアプローチを使用して手動でコーディングするのではなく、ステートマシンがコードとして表現されると、それをサポートするために生成する必要があるボイラープレートの重みで基礎となる構造が消える傾向があるためです。このアプローチを使用すると、懸念事項を非常に分離し、ステートマシンの構造を「可視」に保つことができます。自動生成されたコードは、触れる必要のないモジュールに入ります。そのため、記述したサポートコードに影響を与えることなく、状態マシンの構造に戻って操作することができます。

申し訳ありませんが、私は熱狂的すぎて、間違いなく全員を先延ばしにしています。しかし、これは一流のユーティリティであり、十分に文書化されています。


20

C / C ++ Users Journalの記事が優れていたMiro Samek(ブログState Space、ウェブサイトState Machines&Tools)の作業を必ずチェックしてください。

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

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

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


私はあなたのしゃれに従って質問のタイトルを変更しました。
jldupont 2009年

@jldupont:明確にした方がいいということです。私は今私の答えの無関係な部分を削除しました。
ダニエルダラナス

1
私は自分でソフトウェアをうまく​​使用したので、ウェブサイト/本に何を期待するかを追加しました。それは私の本棚で最高の本です。
Adriaan、2011

@エイドリアン、素晴らしい説明!ウェブサイトのホームを変更したところ、以前のリンクが機能しなくなりました。
Daniel Daranas、2011

2
リンクが機能していないか、組み込みソフトウェアへの方向を変えたと思われるサイトのホームページを指している。state-machine.com/resources/articles.phpの一部のコンテンツは引き続き表示されますが、ステートマシン関連のリンクのほとんどが表示されていません。これは、そこにある唯一の優れたリンクの1つです。state
machine.com

11

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

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


1
このソリューションは適切にスケーリングされないように感じます。テーブルの埋め込みが多すぎますか?
jldupont 2009年

2
+1。ここでのスケーリングの問題はメモリです-私自身のソリューションには、時間に関するスケーリングの問題があります。つまり、遷移テーブルのスキャンにかかる時間です(ただし、最も一般的な遷移を手動で最適化できます)。これは速度のためにメモリを犠牲にします-それは単なるトレードオフです。おそらく境界のチェックが必要でしょうが、それは悪い解決策ではありません。
paxdiablo 2009年

みんな-私のコメントは意図したとおりに出てこなかった:私はそれがはるかに面倒でエラーが発生しやすいことを意味しました。状態/イベントを追加する場合、多くの編集を行う必要があります。
jldupont 2009年

3
2D配列が手動で初期化されたと言う人はいません。おそらく、構成ファイルを読み取ってそれを作成するものがあるかもしれません(または少なくとも確かに存在する可能性があります)。
John Zwinck、2009年

このような配列を初期化する1つの方法は、プリバインディングをレイトバインダーとして使用することです(アーリーバインディングとは対照的)。STATE_LISTマクロを使用するときにエントリマクロを(再)定義するすべての状態のリスト#define STATE_LIST() \STATE_LIST_ENTRY(state1)\STATE_LIST_ENTRY(state2)\...(各の後に暗黙の改行\ )を定義します。例-状態名の配列の作成:#define STATE_LIST_ENTRY(s) #s , \n const char *state_names[] = { STATE_LIST() };\n #undef STATE_LIST_ENTRY。最初に設定する作業もありますが、これは非常に強力です。新しい状態を追加->ミスがないことが保証されています。
フロブダル

9

非常に優れたテンプレートベースのC ++ステートマシン「フレームワーク」は、Stefan Heinzmannの記事で紹介されています

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

ここでの主な革新は、コンパイラーが非常に効率的なコードを生成していることです。空の入口/出口アクションには費用がかかりません。空でないエントリ/終了アクションはインライン化されます。コンパイラーは、ステートチャートの完全性も検証しています。アクションがないと、リンクエラーが発生します。捕まらない唯一のものは行方不明Top::initです。

Samekのコードは設計により終了/遷移を処理しないのに対し、これは、Miro Samekの実装に代わる非常に優れた方法であり、何も欠落せずに対応できる場合、これはUMLセマンティクスを正しく実装しますが、完全なUMLステートチャート実装からはほど遠いです。 / 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)

コードにsthがありません。まず、2つのヘッダーを含めますが、最初のヘッダーのみを指定します。"include"ステートメントをコメント化すると、コンパイル時に次のエラーが発生します:d:\ 1 \ hsm> g ++ test.cpp test.cpp:195:1:error:specialization of 'static void CompState <H、id、B> :: init(H&)[H = TestHSM; unsigned int id = 0u; B = CompState <TestHSM、0u、TopState <TestHSM>>] 'インスタンス化後
Freddie Chopin

私はすべてのHSMINIT()の定義をTestHSMクラスの上に移動する必要があり、コンパイルして正常に動作します(;間違っているのは、すべての遷移が「外部」であり、「内部」でなければならないという事実だけです。記事でそれについていくつかの議論と著者「extrenalは」右でしたが、矢は「内部」をお勧め使用することを決めた。
フレディショパン

5

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

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);
    }
 }

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


+1は本当に便利で、トランジション関数内の機能を手に入れるための素晴らしい場所を提供します
Fire Crow

5

最も単純なケース

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

一般的なプログラミング

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

2次元テーブル

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

免責事項

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


これが実際に予約語でなくても、関連付けのための変数名または記号としての「this」は避けます。
XTL

4

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

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;
}

14
「非常にテストされていない」コメントが大好きです。テストされていない度合いがあり、テストしないようにかなりの努力を払ったことを示しているようです:-)
paxdiablo

@Christophこの回答のリンクは壊れています。また、このコードをテストしたかどうか。テスト済みで機能している場合は、回答から削除する必要があります。また、マクロが展開された後のコード例を示すこともできます。私は一般的な考えが好きです。
ジョアキム

4

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

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

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


2018年にこれを見つけ、それはまだ適用可能です。私は@paxdiabloの回答を読んでいましたが、以前は組み込みシステムでそのタイプの実装を正常に使用していました。このソリューションは、paxdiablos回答から欠落しているものを追加します:)
Kristoffer

4

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


3

これは(いつものように)遅くなりますが、今日までの回答をスキャンすると、重要な何かが欠けていると思います。

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

また、オプションの新しい状態と、表の各セルのオプションの関数ポインターも含めます。関数ポインタは、アトミックアクションのリストを起動するだけでは不十分な例外的なケースのためにあります。

新しいコードを記述せずに、テーブルを編集するだけでさまざまな機能を表現できる場合は、正しく実行していることがわかります。


2
多分例がいいでしょう、いいえ?
jldupont 2009年

1
単独で提示できる現実的な例は、私が現時点で与える準備ができているよりも多くの時間を必要とする挑戦的なタスクです。私の投稿に特に理解しにくいことはありますか?もっとはっきり表現できるかもしれません。アイデアは非常に単純です。すべてのイベント/状態の組み合わせに対して個別の関数を必要とする状態メカニズムを定義しないでください。そのようにすると、あまりにも多くの関数が得られます。代わりに、少なくともほとんどの場合、そのイベント/状態の組み合わせに必要な機能を説明する別の方法を見つけてください。
Bill Forster、

2
理解:疑似コードの例は良かったのですが、あなたの要点は明らかです。
jldupont 2009年

3

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

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

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

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

/* 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;
    }
}

2
これは、パーサーまたはレクサージェネレーターが喜んで出力するものです。むちゃくちゃそう。手作業でコーディングするかどうかは疑問です。もちろん、教育的なメリットがあります。
モニカ

3

boost.orgには、2つの異なるステートチャート実装が付属しています。

いつものように、ブーストはテンプレート地獄にあなたを照らします。

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

ここだ2間の比較を求めSOの質問著者の両方に対応しています。



2

これをどこかに見た

#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);
  }
}

1
それは興味深いですが、例を1つまたは2つ(そしておそらくマクロ化されていない結果)を出すか、なぜこれが他の方法よりも実用的であるかについて議論するまで、賛成票を投じないでください。孤立したブラケットとマクロの興味深い使い方。何らかの末尾再帰の最適化を行う言語でも、同様のことができると思います。あなたはまっすぐな関数呼び出しを使うことができ、関数呼び出しのガベージでスタック空間をオーバーロードすることを心配する必要はありません(これはマクロが本質的にここで克服しているものだと思います)
Ape-in​​ago 09/10/30

2
この方法の利点は...?マクロを難読化するなどのいくつかの欠点があり、gotoそれを使用すると、模倣的なマルチタスクOSへの依存関係が作成されます。
Craig McQueen

2

C ++を使用してOOコードを使用できることを示唆しているので、「GoF」状態パターンを評価することをお勧めします(GoF = Gang of Four、デザインパターンを脚光を浴びたデザインパターンの本を書いた人)。

特に複雑ではなく、広く使用および議論されているため、例や説明をオンラインで簡単に見ることができます。

また、後日あなたのコードを保守している他の人にも認識される可能性が非常に高くなります。

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

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


コメントのように見えます:そのように扱うことを提案できますか?つまり、「回答」セクションに配置しないでください。
jldupont 2011

「GoF状態パターン」に慣れていない人のために、適切なURLリンクを提供できればよいでしょう。
Craig McQueen

1
@jldupont-公正なコメント。私は、特定のパフォーマンスの問題がない限り、GoFのアプローチは罰金を動作し、比較的大きな「ユーザーベース」を持っているだろう、という個人的な経験に基づいて感じるように適切な答えにするために、テキストを変更
ミック・

@Craig-いくつかのリンクを追加しました。どちらも私が追加した時点では正確で明確に見えました。
Mick、

2

これは、メッセージキューをイベントとして使用する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;
    }
}

Makefile

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

1

あなたの質問はかなり一般的です、
ここに役立つかもしれない2つの参考記事があります、

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

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

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

      この記事の私の関心は、ステートマシンの基礎と、CまたはC ++でステートマシンをコーディングするための簡単なプログラミングガイドラインです。これらの単純な手法がより一般的になり、ソースコードからステートマシンの構造を簡単に確認できるようになることを願っています。



1

これは多くの回答が含まれている古い投稿ですが、Cの有限状態マシンに独自のアプローチを追加するつもりでした。Pythonスクリプトを作成して、任意の数の状態のスケルトンCコードを生成しました。そのスクリプトはFsmTemplateCのGituHubに文書化されています

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

FsmTemplateCを使用してスケルトンCコードを生成するターンスタイルの例の Pythonスクリプトを次に示します。

# 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->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);
}

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


0

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

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


0
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;
}

関数への定数関数ポインターの配列を使用することにより、安全性をさらに最適化できます
AlphaGoku

0

私は個人的に、自己参照構造体をポインター配列と組み合わせて使用​​しています。しばらく前にgithubにチュートリアルをアップロードしました。リンク:

https://github.com/mmelchger/polling_state_machine_c

注:このスレッドはかなり古くなっていますが、ステートマシンの設計についての意見や考えを知り、Cで可能なステートマシン設計の例を提供できるようにしたいと思っています。


0

UML-state-machine-in-cはCの「軽量」状態マシンフレームワークと考えることができます。このフレームワークは、有限状態マシン階層型状態マシンの両方をサポートするように作成しました。ステートテーブルや単純なスイッチケースと比較して、フレームワークアプローチはよりスケーラブルです。単純な有限状態機械から複雑な階層状態機械まで使用できます。

状態機械はstate_machine_t構造によって表されます。2つのメンバー「Event」と「state_t」へのポインターのみが含まれています。

struct state_machine_t
{
   uint32_t Event;          //!< Pending Event for state machine
   const state_t* State;    //!< State of state machine.
};

state_machine_tステートマシン構造の最初のメンバーである必要があります。例えば

struct user_state_machine
{
  state_machine_t Machine;    // Base state machine. Must be the first member of user derived state machine.

  // User specific state machine members
  uint32_t param1;
  uint32_t param2;
  ...
};

state_t 状態のハンドラーと、開始および終了アクションのオプションのハンドラーが含まれます。

//! finite state structure
struct finite_state{
  state_handler Handler;      //!< State handler to handle event of the state
  state_handler Entry;        //!< Entry action for state
  state_handler Exit;          //!< Exit action for state.
};

フレームワークが階層状態マシン用に構成されている場合、 state_t親と子の状態へのポインターが含まれます。

フレームワークは、dispatch_event状態マシンにイベントをディスパッチし、switch_state状態遷移をトリガーするAPI を提供します。

階層型ステートマシンの実装方法の詳細については、GitHubリポジトリを参照してください。

コード例、

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


-1

以下は、各関数が独自の状態セットを持つことができるようにマクロを使用するステートマシンのメソッドです。https//www.codeproject.com/Articles/37037/Macros-to-simulate-multi-tasking-blocking-code -で

それは「マルチタスクのシミュレーション」と題されていますが、それが唯一の用途ではありません。

このメソッドは、コールバックを使用して、中断した各関数をピックアップします。各関数には、各関数に固有の状態のリストが含まれています。中央の「アイドルループ」は、ステートマシンを実行するために使用されます。「アイドルループ」は、ステートマシンがどのように機能するかを知りません。「何をすべきかを知っている」のは個々の機能です。関数のコードを記述するために、状態のリストを作成し、マクロを使用して「一時停止」および「再開」するだけです。Nexus 7000スイッチのトランシーバーライブラリを書いたとき、これらのマクロをシスコで使用しました。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.