Cでステートマシンを書く最良の方法は何ですか?
私は通常、for(;;) に大きな switch-case ステートメントを記述し、外部操作が終了したときにステート マシンに再度入るコールバックを使用します。
もっと効率的な方法を知っていますか?
9 に答える
QuantumLeapsのアプローチが好きです。
現在の状態は、イベントオブジェクトを引数として取る関数へのポインタです。イベントが発生したら、そのイベントで状態関数を呼び出すだけです。その後、関数は、状態を別の関数に設定するだけで、その作業を実行して別の状態に遷移できます。
例えば:
// State type and variable, notice that it's a function pointer.
typedef void (*State)(int);
State state;
// A couple of state functions.
void state_xyz(int event) { /*...*/ }
void state_init(int event) {
if (event == E_GO_TO_xyz) {
// State transition done simply by changing the state to another function.
state = state_xyz;
}
}
// main contains the event loop here:
int main() {
int e;
// Initial state.
state = state_init;
// Receive event, dispatch it, repeat... No 'switch'!
while ((e = wait_for_event()) != E_END) {
state(e);
}
return 0;
}
QLフレームワークは、エントリ/終了/初期化アクション、階層型ステートマシンなどの追加機能のヘルパーを提供します。これについてのより深い説明と適切な実装については、この本を強くお勧めします。
最善の方法は主に主観的ですが、一般的な方法は、状態コード(列挙型またはその他の整数型)を関数ポインターにマップする「テーブルベース」のアプローチを使用することです。この関数は次の状態とその他の関連データを返し、最終状態に達するまでこれをループします。これは実際、上記のアプローチとして説明していることかもしれません。
これはほとんど標準的なアプローチです。よく考えられているライブラリを研究し、詳細を比較することに興味がある場合は、Ragelをご覧ください。
Ragelは、実行可能な有限状態マシンを正規言語からコンパイルします。Ragelは、C、C ++、Objective-C、D、Java、Rubyを対象としています。Ragelステートマシンは、正規表現マシンのようにバイトシーケンスを認識するだけでなく、正規言語の認識の任意のポイントでコードを実行することもできます。コードの埋め込みは、正規言語の構文を乱さないインライン演算子を使用して行われます。
Switch ステートメントは開始するのに適した方法ですが、FSM が大きくなると扱いにくくなる傾向があります。
優れた情報とアイデアを含むいくつかの関連する (または重複する) SO の質問:
別のアプローチは、状態/イベントの組み合わせごとに、実行するアクションと次に進むべき状態を記述する 2D 配列です。これは、「状況」に応じてさまざまな状態に移行する必要がある場合に管理が難しくなる可能性がありますが、うまく機能させることができます。次のイベントを返すイベント認識関数があります。呼び出された関数がその状態をオーバーライドしない限り、テーブル内の各エントリがイベントの受信時に呼び出す関数と次の状態に移動することを識別するテーブルがあります。
実際にそのようなコードを生成するのは面倒です - それは最初にFSMがどのように記述されているかに依存します. 重複したアクションを見つけることは、多くの場合重要です。多くの場合、エラー処理を明示的に記録しない「疎行列」手法に頼ることができます。エントリが疎行列に論理的に存在する場合、そのイベント/状態情報に基づいて行動しますが、エントリが存在しない場合は、適切にフォールバックしますエラー報告と再同期コード。
構造体へのポインターの 2D 配列は、汎用 FSM 関数に渡すことができます。トリプルポインターを書くという事実は、何が起こっているのかについて慎重になるのに十分です. (私は 1986 年 3 月にそれらの 1 つを書きました - それを説明したドキュメントの印刷物はまだ持っていますが、ディスク上のソースはもうありません。)
このパターンを使用しました。典型的なステート マシンの実装パターンはありますか? (ベストアンサーにチェックを入れてください)。
しかし、いくつかの機能も追加します
1.以前の状態に関する情報。
2. パラメータの受け渡し
3. グローバル タイムアウトや「SM のリセット」などの外部イベントの追加
私は、ステート マシンの暗号化と保守性が少し劣っていることに気付きました。
とにかく、私はまだステートマシンが最も難しくて面倒なプログラミング作業だと思っています.
こちらをご覧ください: http://code.google.com/p/fwprofile/
これは、C で実装されたステート マシンのオープン ソース バージョン (GNU GPLv3) です。この概念と実装は、ミッション クリティカルなアプリケーションでの使用に適しています。産業用アプリケーションでの展開があります。
c で実装された最小限の UML-state-machineフレームワークを使用できます。有限ステート マシンと階層ステート マシンの両方をサポートします。フレームワークは非常にミニマリストです。3 つの API、2 つの構造体、および 1 つの列挙しかありません。
ステート マシンは構造体で表されstate_machine_t
ます。ステート マシンを作成するために継承できる抽象構造です。
//! Abstract state machine structure
struct state_machine_t
{
uint32_t Event; //!< Pending Event for state machine
const state_t* State; //!< State of state machine.
};
state_t
状態は、フレームワーク内の構造体へのポインターによって表されます。
フレームワークが有限状態マシン用に構成されている場合、次のものstate_t
が含まれます。
typedef struct finite_state_t state_t;
// finite state structure
typedef struct finite_state_t{
state_handler Handler; //!< State handler function (function pointer)
state_handler Entry; //!< Entry action for state (function pointer)
state_handler Exit; //!< Exit action for state (function pointer)
}finite_state_t;
フレームワークが階層ステート マシンをサポートするように構成されている場合。これには、状態間の階層関係を表す追加の 3 つのメンバーが含まれています。
typedef struct hierarchical_state_t state_t;
//! Hierarchical state structure
typedef struct hierarchical_state_t
{
state_handler Handler; //!< State handler function
state_handler Entry; //!< Entry action for state
state_handler Exit; //!< Exit action for state.
const state_t* const Parent; //!< Parent state of the current state.
const state_t* const Node; //!< Child states of the current state.
uint32_t Level; //!< Hierarchy level from the top state.
}hierarchical_state_t;
フレームワークはdispatch_event
、ステート マシンにイベントをディスパッチする API と、ステート トラバーサル用の 2 つの API を提供します。
state_machine_result_t dispatch_event(state_machine_t* const pState_Machine[], uint32_t quantity);
state_machine_result_t switch_state(state_machine_t* const pState_Machine, const state_t* pTarget_State);
state_machine_result_t traverse_state(state_machine_t* const pState_Machine, const state_t* pTarget_State);
詳細については、GitHubプロジェクトを参照してください。