127

Cで単純なステートマシンを実装する必要があります。
標準のswitchステートメントが最善の方法ですか?
現在の状態(状態)と遷移のトリガーがあります。


switch(state)
{
  case STATE_1:
     state = DoState1(transition);
     break;
  case STATE_2:
     state = DoState2(transition);
     break;
}
...
DoState2(int transition)
{
   // Do State Work
   ...
   if(transition == FROM_STATE_2) {
     // New state when doing STATE 2 -> STATE 2
   }
   if(transition == FROM_STATE_1) {
    // New State when moving STATE 1 -> STATE 2
   }
   return new_state;
}

単純なステートマシンのためのより良い方法はありますか

編集:C ++の場合、 BoostStatechartライブラリが最適な方法だと思います。ただし、Cには役立ちませ。Cのユースケースに集中しましょう。

4

20 に答える 20

152

私は、ほとんどのステート マシンでテーブル駆動型のアプローチを使用することを好みます。

typedef enum { STATE_INITIAL, STATE_FOO, STATE_BAR, NUM_STATES } state_t;
typedef struct instance_data instance_data_t;
typedef state_t state_func_t( instance_data_t *data );

state_t do_state_initial( instance_data_t *data );
state_t do_state_foo( instance_data_t *data );
state_t do_state_bar( instance_data_t *data );

state_func_t* const state_table[ NUM_STATES ] = {
    do_state_initial, do_state_foo, do_state_bar
};

state_t run_state( state_t cur_state, instance_data_t *data ) {
    return state_table[ cur_state ]( data );
};

int main( void ) {
    state_t cur_state = STATE_INITIAL;
    instance_data_t data;

    while ( 1 ) {
        cur_state = run_state( cur_state, &data );

        // do other program logic, run other state machines, etc
    }
}

これはもちろん、複数のステート マシンなどをサポートするように拡張できます。遷移アクションにも対応できます。

typedef void transition_func_t( instance_data_t *data );

void do_initial_to_foo( instance_data_t *data );
void do_foo_to_bar( instance_data_t *data );
void do_bar_to_initial( instance_data_t *data );
void do_bar_to_foo( instance_data_t *data );
void do_bar_to_bar( instance_data_t *data );

transition_func_t * const transition_table[ NUM_STATES ][ NUM_STATES ] = {
    { NULL,              do_initial_to_foo, NULL },
    { NULL,              NULL,              do_foo_to_bar },
    { do_bar_to_initial, do_bar_to_foo,     do_bar_to_bar }
};

state_t run_state( state_t cur_state, instance_data_t *data ) {
    state_t new_state = state_table[ cur_state ]( data );
    transition_func_t *transition =
               transition_table[ cur_state ][ new_state ];

    if ( transition ) {
        transition( data );
    }

    return new_state;
};

テーブル駆動型のアプローチは、保守と拡張が容易で、状態図へのマッピングが簡単です。

于 2008-09-25T13:35:31.257 に答える
27

私がFSMについて言及した別のCの質問に対する私の答えを見たことがあるかもしれません!これが私がそれをする方法です:

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

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

次のマクロを定義します

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

これは、特定のケースに合わせて変更できます。たとえば、FSMFILEFSMを駆動したいファイルがある場合、次の文字を読み取るアクションをマクロ自体に組み込むことができます。

#define FSM
#define STATE(x)         s_##x : FSMCHR = fgetc(FSMFILE); sn_##x :
#define NEXTSTATE(x)     goto s_##x
#define NEXTSTATE_NR(x)  goto sn_##x

これで、2つのタイプの遷移があります。1つは状態に移行して新しい文字を読み取り、もう1つは入力を消費せずに状態に移行します。

次のような方法でEOFの処理を自動化することもできます。

#define STATE(x)  s_##x  : if ((FSMCHR = fgetc(FSMFILE) == EOF)\
                             goto sx_endfsm;\
                  sn_##x :

#define ENDFSM    sx_endfsm:

このアプローチの良い点は、描画した状態図を作業コードに直接変換でき、逆に、コードから状態図を簡単に描画できることです。

FSMを実装するための他の手法では、遷移の構造は制御構造に埋め込まれ(一方、if、switch ...)、変数値(通常はstate変数)によって制御されます。複雑なコード。

このテクニックは、残念ながらもう発行されていない素晴らしい「ComputerLanguage」誌に掲載された記事から学びました。

于 2008-09-25T13:35:49.133 に答える
15

私もテーブルアプローチを使用しました。ただし、オーバーヘッドがあります。なぜポインタの2番目のリストを保存するのですか?()のないCの関数は、constポインターです。だからあなたはすることができます:

struct state;
typedef void (*state_func_t)( struct state* );

typedef struct state
{
  state_func_t function;

  // other stateful data

} state_t;

void do_state_initial( state_t* );
void do_state_foo( state_t* );
void do_state_bar( state_t* );

void run_state( state_t* i ) {
    i->function(i);
};

int main( void ) {
    state_t state = { do_state_initial };

    while ( 1 ) {
        run_state( state );

        // do other program logic, run other state machines, etc
    }
}

もちろん、恐れの要因(つまり、安全性と速度)によっては、有効なポインターを確認することをお勧めします。3つ程度のステートよりも大きいステートマシンの場合、上記のアプローチは、同等のスイッチまたはテーブルアプローチよりも少ない命令である必要があります。次のようにマクロ化することもできます。

#define RUN_STATE(state_ptr_) ((state_ptr_)->function(state_ptr_))

また、OPの例から、ステートマシンを考えたり設計したりするときに行うべき単純化があると感じています。遷移状態をロジックに使用する必要はありません。各状態関数は、過去の状態を明示的に知らなくても、指定された役割を実行できる必要があります。基本的に、現在の状態から別の状態に移行する方法を設計します。

最後に、「機能」境界に基づいてステートマシンの設計を開始するのではなく、そのためにサブ機能を使用します。代わりに、続行する前に何かが発生するのをいつ待つ必要があるかに基づいて状態を分割します。これにより、結果が得られるまでにステートマシンを実行する必要がある回数を最小限に抑えることができます。これは、I/O関数または割り込みハンドラーを作成するときに重要になる可能性があります。

また、古典的なswitchステートメントのいくつかの長所と短所:

長所:

  • それはその言語で書かれているので、文書化されて明確です
  • 状態は、それらが呼び出される場所で定義されます
  • 1回の関数呼び出しで複数の状態を実行できます
  • すべての状態に共通のコードは、switchステートメントの前後で実行できます

短所:

  • 1回の関数呼び出しで複数の状態を実行できます
  • すべての状態に共通のコードは、switchステートメントの前後で実行できます
  • スイッチの実装は遅くなる可能性があります

賛否両論の2つの属性に注意してください。この切り替えにより、州間で共有しすぎる機会が生まれ、州間の相互依存関係が管理できなくなる可能性があると思います。ただし、状態の数が少ない場合は、最も読みやすく、保守しやすい場合があります。

于 2012-02-17T03:37:59.173 に答える
11

ステートマシンが大きくなるにつれて、より保守しやすいロジックグリッドもあります

于 2008-09-25T13:24:00.643 に答える
10

単純なステート マシンの場合は、状態に switch ステートメントと列挙型を使用するだけです。入力に基づいて、switch ステートメント内で遷移を行います。実際のプログラムでは、明らかに「if(input)」を変更して遷移点を確認します。お役に立てれば。

typedef enum
{
    STATE_1 = 0,
    STATE_2,
    STATE_3
} my_state_t;

my_state_t state = STATE_1;

void foo(char input)
{
    ...
    switch(state)
    {
        case STATE_1:
            if(input)
                state = STATE_2;
            break;
        case STATE_2:
            if(input)
                state = STATE_3;
            else
                state = STATE_1;
            break;
        case STATE_3:
            ...
            break;
    }
    ...
}
于 2008-09-25T19:36:45.427 に答える
4

単純なケースでは、スイッチスタイルの方法を使用できます。過去にうまく機能していることがわかったのは、遷移にも対処することです。

static int current_state;    // should always hold current state -- and probably be an enum or something

void state_leave(int new_state) {
    // do processing on what it means to enter the new state
    // which might be dependent on the current state
}

void state_enter(int new_state) {
    // do processing on what is means to leave the current state
    // might be dependent on the new state

    current_state = new_state;
}

void state_process() {
    // switch statement to handle current state
}
   

Boostライブラリについては何も知りませんが、このタイプのアプローチは非常に単純で、外部の依存関係を必要とせず、実装も簡単です。

于 2008-09-25T13:25:30.903 に答える
4

私は edx.org のコース、Embedded Systems - Shape the World UTAustinX - UT.6.02x、第 10 章、Jonathan Valvano と Ramesh Yerraballi で Moore FSM の非常に洗練された C 実装を見つけました....

struct State {
  unsigned long Out;  // 6-bit pattern to output
  unsigned long Time; // delay in 10ms units 
  unsigned long Next[4]; // next state for inputs 0,1,2,3
}; 

typedef const struct State STyp;

//this example has 4 states, defining constants/symbols using #define
#define goN   0
#define waitN 1
#define goE   2
#define waitE 3


//this is the full FSM logic coded into one large array of output values, delays, 
//and next states (indexed by values of the inputs)
STyp FSM[4]={
 {0x21,3000,{goN,waitN,goN,waitN}}, 
 {0x22, 500,{goE,goE,goE,goE}},
 {0x0C,3000,{goE,goE,waitE,waitE}},
 {0x14, 500,{goN,goN,goN,goN}}};
unsigned long currentState;  // index to the current state 

//super simple controller follows
int main(void){ volatile unsigned long delay;
//embedded micro-controller configuration omitteed [...]
  currentState = goN;  
  while(1){
    LIGHTS = FSM[currentState].Out;  // set outputs lines (from FSM table)
    SysTick_Wait10ms(FSM[currentState].Time);
    currentState = FSM[currentState].Next[INPUT_SENSORS];  
  }
}
于 2015-04-29T02:10:10.400 に答える
4

switch() は、C でステート マシンを実装するための強力で標準的な方法ですが、状態が多数ある場合は保守性が低下する可能性があります。もう 1 つの一般的な方法は、関数ポインターを使用して次の状態を格納することです。この簡単な例では、セット/リセット フリップフロップを実装しています。

/* Implement each state as a function with the same prototype */
void state_one(int set, int reset);
void state_two(int set, int reset);

/* Store a pointer to the next state */
void (*next_state)(int set, int reset) = state_one;

/* Users should call next_state(set, reset). This could
   also be wrapped by a real function that validated input
   and dealt with output rather than calling the function
   pointer directly. */

/* State one transitions to state one if set is true */
void state_one(int set, int reset) {
    if(set)
        next_state = state_two;
}

/* State two transitions to state one if reset is true */
void state_two(int set, int reset) {
    if(reset)
        next_state = state_one;
}
于 2008-09-25T20:58:42.343 に答える
2

この記事は状態パターンに適しています (ただし、C++ であり、具体的には C ではありません)。

「 Head First Design Patterns 」という本を手に入れることができれば、説明と例は非常に明確です。

于 2008-09-25T13:26:37.250 に答える
2

libero FSM ジェネレーター ソフトウェアを調べることをお勧めします。状態記述言語および/または (Windows) 状態図エディタから、C、C++、Java およびその他多くのコードを生成することができます...さらに、優れたドキュメントと図。iMatixからのソースとバイナリ

于 2008-09-26T14:07:19.133 に答える
1

私の経験では、「switch」ステートメントを使用することが、複数の可能な状態を処理するための標準的な方法です。遷移値を状態ごとの処理に渡していることに驚いていますが。ステートマシンの要点は、各ステートが単一のアクションを実行することだと思いました。次に、次のアクション/入力によって、どの新しい状態に移行するかが決定されます。したがって、各状態処理関数は、状態に入るために修正されたものをすぐに実行し、その後、別の状態に遷移する必要があるかどうかを判断することを期待していました。

于 2008-09-25T13:13:05.397 に答える
1

Practical Statecharts in C/C++というタイトルの本があります。しかし、私たちが必要とするものには重すぎます。

于 2008-09-25T13:34:21.210 に答える
0

あなたの質問は「典型的なデータベースの実装パターンはありますか」に似ていますか?答えはあなたが何を達成したいかによりますか?より大きな決定論的ステートマシンを実装する場合は、モデルとステートマシンジェネレーターを使用できます。例はwww.StateSoft.org-SMギャラリーで見ることができます。Janusz Dobrowolski

于 2010-03-10T19:36:23.437 に答える
0

C ++では、Stateパターンを検討してください。

于 2008-09-25T13:23:27.337 に答える
-1

Boostにはステートチャートライブラリがあります。http://www.boost.org/doc/libs/1_36_0/libs/statechart/doc/index.html

しかし、私はそれの使用について話すことはできません。自分では使用していません(まだ)

于 2008-09-25T13:11:48.020 に答える