일반적인 상태 머신 구현 패턴이 있습니까?


118

우리는 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 ++의 경우 Boost Statechart 라이브러리가 갈 길이 라고 생각합니다 . 그러나 C 에서는 도움 이 되지 않습니다 . C 사용 사례에 집중 하겠습니다 .


답변:


134

대부분의 상태 머신에 테이블 기반 접근 방식을 선호합니다.

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

테이블 기반 접근 방식은 유지 관리 및 확장이 더 쉽고 상태 다이어그램에 매핑하는 것이 더 간단합니다.


시작하는 아주 좋은 방법, 적어도 저에게는 시작점입니다. 한 가지주의 할 점은 run_state ()의 첫 번째 줄에 "." 거기 있으면 안됩니다.
Atilla Filiz

2
이 답변이 다른 두 가지 접근 방식에 대해 적어도 두 단어를 말하면 더 좋을 것입니다. 큰 스위치 케이스가있는 "글로벌"방법, 상태 디자인 패턴으로 상태를 분리 하고 각 상태가 자체 전환을 처리하도록하는 것입니다.
erikbwork 2013 년

안녕, 나는이 게시물이 오래되었다는 것을 알고 있지만 내 대답을 얻길 바랍니다. :) instance_data_t 변수에서 무엇을해야합니까? 인터럽트에서 상태를 변경하는 방법이 궁금합니다 ...이 변수에 처리 된 인터럽트에 대한 정보를 저장하는 좋은 방법입니까? 예를 들어 버튼을 눌렀다는 정보를 저장하여 상태를 변경해야합니다.
grongor

@GRoNGoR 이벤트 기반 상태 시스템을 다루는 것처럼 들립니다. 실제로 이벤트 데이터를 저장하는 데 사용할 수 있다고 생각합니다.
Zimano 2015 년

3
NUM_STATES가 정의 된 방식에 대해 정말 감사합니다.
Albin Stigo

25

내가 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

이제 두 가지 유형의 전환이 있습니다. 하나는 상태로 이동하고 새 문자를 읽고 다른 하나는 입력을 사용하지 않고 상태로 이동합니다.

다음과 같이 EOF 처리를 자동화 할 수도 있습니다.

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

#define ENDFSM    sx_endfsm:

이 접근 방식의 좋은 점은 사용자가 그린 상태 다이어그램을 작업 코드로 직접 변환 할 수 있다는 것과 반대로 코드에서 상태 다이어그램을 쉽게 그릴 수 있다는 것입니다.

FSM을 구현하는 다른 기술에서 전환의 구조는 제어 구조에 묻히고 (If, switch ...) 변수 값 (일반적으로 a state 변수)에 , nice 다이어그램을 a와 연결하는 것은 복잡한 작업 일 수 있습니다. 복잡한 코드.

나는 불행히도 더 이상 출판되지 않는 위대한 "컴퓨터 언어"잡지에 실린 기사에서이 기술을 배웠다.


1
기본적으로 좋은 FSM은 가독성에 관한 것입니다. 이것은 좋은 인터페이스를 제공하고 구현은 얻는 것만 큼 좋습니다. 언어에 기본 FSM 구조가 없다는 것은 부끄러운 일입니다. 이제 C1X에 늦게 추가 된 것으로 볼 수 있습니다!
Kelden Cowan

3
임베디드 애플리케이션에 대한이 접근 방식을 좋아합니다. 이벤트 기반 상태 시스템에서이 접근 방식을 사용하는 방법이 있습니까?
ARF

13

나는 또한 테이블 접근 방식을 사용했습니다. 그러나 오버 헤드가 있습니다. 두 번째 포인터 목록을 저장하는 이유는 무엇입니까? ()가없는 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 문의 몇 가지 장단점 :

장점 :

  • 언어로되어 있으므로 문서화되고 명확합니다.
  • 상태는 호출되는 위치에 정의됩니다.
  • 하나의 함수 호출로 여러 상태를 실행할 수 있습니다.
  • 모든 상태에 공통적 인 코드는 switch 문 전후에 실행할 수 있습니다.

단점 :

  • 하나의 함수 호출로 여러 상태를 실행할 수 있습니다.
  • 모든 상태에 공통적 인 코드는 switch 문 전후에 실행할 수 있습니다.
  • 스위치 구현이 느릴 수 있음

찬반 양론 인 두 가지 속성에 유의하십시오. 저는 스위치가 상태간에 너무 많은 공유 기회를 허용하고 상태 간의 상호 의존성이 관리 불가능해질 수 있다고 생각합니다. 그러나 적은 수의 상태의 경우 가장 읽기 쉽고 유지 관리가 가능할 수 있습니다.


10

단순한 상태 머신의 경우 상태에 대한 스위치 문과 열거 형 유형을 사용하면됩니다. 입력에 따라 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;
    }
    ...
}

1
함수 내부에 "상태"를 넣고 정적으로 만들 가치가있을 수 있습니다.
Steve Melnikoff

2
@Steve Melnikoff : 상태 머신이 하나 뿐인 경우에만 가능합니다. 함수 외부에두면 자체 상태를 가진 상태 머신 배열을 가질 수 있습니다.
Vicky

@Vicky : 하나의 함수는 필요한 경우 상태 변수 배열과 함께 원하는만큼의 상태 머신을 포함 할 수 있으며, 다른 곳에서 사용되지 않는 경우 함수 내부 (정적 변수로)에있을 수 있습니다.
Steve Melnikoff 2010-06-28

10

에서 마틴 파울러의 UML 증류 , 그는 (웃기 의도 없음) 제 10 장 상태 머신 다이어그램 (강조 광산)의 상태 :

상태 다이어그램은 중첩 스위치 , 상태 패턴상태 테이블의 세 가지 주요 방법으로 구현할 수 있습니다 .

휴대폰 디스플레이 상태의 간단한 예를 사용하겠습니다.

여기에 이미지 설명 입력

중첩 스위치

Fowler는 C # 코드의 예를 제공했지만 제 예에 적용했습니다.

public void HandleEvent(PhoneEvent anEvent) {
    switch (CurrentState) {
    case PhoneState.ScreenOff:
        switch (anEvent) {
        case PhoneEvent.PressButton:
            if (powerLow) { // guard condition
                DisplayLowPowerMessage(); // action
                // CurrentState = PhoneState.ScreenOff;
            } else {
                CurrentState = PhoneState.ScreenOn;
            }
            break;
        case PhoneEvent.PlugPower:
            CurrentState = PhoneState.ScreenCharging;
            break;
        }
        break;
    case PhoneState.ScreenOn:
        switch (anEvent) {
        case PhoneEvent.PressButton:
            CurrentState = PhoneState.ScreenOff;
            break;
        case PhoneEvent.PlugPower:
            CurrentState = PhoneState.ScreenCharging;
            break;
        }
        break;
    case PhoneState.ScreenCharging:
        switch (anEvent) {
        case PhoneEvent.UnplugPower:
            CurrentState = PhoneState.ScreenOff;
            break;
        }
        break;
    }
}

상태 패턴

다음은 GoF 상태 패턴을 사용한 예제 구현입니다.

여기에 이미지 설명 입력

상태 테이블

Fowler에서 영감을 얻은 예는 다음과 같습니다.

소스 상태 대상 상태 이벤트 보호 조치
-------------------------------------------------- ------------------------------------
화면 끄기 화면 끄기 누름 버튼 전원 낮음 디스플레이 낮음 전원 메시지  
ScreenOff ScreenOn pressButton! powerLow
ScreenOn ScreenOff pressButton
ScreenOff Screen 충전 플러그 전원
ScreenOn Screen 충전 플러그 전원
화면 충전 화면 꺼짐 전원

비교

중첩 스위치는 모든 로직을 한곳에 보관하지만 상태와 전환이 많으면 코드를 읽기 어려울 수 있습니다. 다른 접근 방식 (다형성 또는 해석 없음)보다 더 안전하고 검증하기 쉽습니다.

상태 패턴 구현은 잠재적으로 로직을 여러 개별 클래스에 분산시켜 전체적으로 이해하는 데 문제가 될 수 있습니다. 반면에 소규모 수업은 개별적으로 이해하기 쉽습니다. 전환을 추가하거나 제거하여 동작을 변경하면 디자인이 특히 취약합니다. 전환은 계층 구조의 메서드이고 코드에 많은 변경이있을 수 있기 때문입니다. 작은 인터페이스의 디자인 원칙에 따라 생활한다면이 패턴이 제대로 작동하지 않는다는 것을 알 수 있습니다. 그러나 상태 머신이 안정적이면 이러한 변경이 필요하지 않습니다.

상태 테이블 접근 방식을 사용하려면 콘텐츠에 대한 일종의 인터프리터를 작성해야합니다 (사용중인 언어에 대한 성찰이있는 경우 더 쉬울 수 있음).이 작업은 미리 수행해야 할 많은 작업이 될 수 있습니다. Fowler가 지적했듯이 테이블이 코드와 분리되어 있으면 다시 컴파일하지 않고도 소프트웨어의 동작을 수정할 수 있습니다. 그러나 이것은 보안에 약간의 영향을 미칩니다. 소프트웨어는 외부 파일의 내용을 기반으로 작동합니다.

편집 (실제로 C 언어 용이 아님)

유창한 인터페이스 (일명 내부 도메인 특정 언어) 접근 방식도 있으며, 이는 아마도 일류 기능 을 가진 언어에 의해 촉진 될 것입니다 . 상태 비 라이브러리가 존재하고 그 블로그 프로그램 코드와 간단한 예제. 자바 구현 (Java8 사전) 논의된다. 나는 나타났다 GitHub의 파이썬 예제 뿐만 아니라.


그림을 만드는 데 어떤 소프트웨어를 사용 했습니까?
sjas



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 atate
    // might be dependent on the new state

    current_state = new_state;
}

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

부스트 라이브러리에 대해 아무것도 모르지만 이러한 유형의 접근 방식은 매우 간단하고 외부 종속성이 필요하지 않으며 구현하기 쉽습니다.


4

switch ()는 C에서 상태 머신을 구현하는 강력하고 표준적인 방법이지만 많은 상태가있는 경우 유지 관리 가능성을 낮출 수 있습니다. 또 다른 일반적인 방법은 함수 포인터를 사용하여 다음 상태를 저장하는 것입니다. 이 간단한 예제는 설정 / 재설정 플립 플롭을 구현합니다.

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

4

edx.org 과정 Embedded Systems-Shape the World UTAustinX-UT.6.02x, 10 장, Jonathan Valvano 및 Ramesh Yerraballi에서 무어 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];  
  }
}

2

libero FSM 생성기 소프트웨어 를 살펴볼 수 있습니다 . 상태 설명 언어 및 / 또는 (윈도우) 상태 다이어그램 편집기에서 C, C ++, Java 및 기타 여러 코드를 생성 할 수 있으며 멋진 문서 및 다이어그램도 생성 할 수 있습니다. iMatix의 소스 및 바이너리



2

내가 가장 좋아하는 패턴 중 하나는 상태 디자인 패턴입니다. 동일한 입력 세트에 대해 다르게 반응하거나 행동합니다.
상태 머신에 대해 switch / case 문을 사용할 때의 문제 중 하나는 더 많은 상태를 만들수록 switch / case가 읽기 / 유지 관리가 더 어려워지고 다루기 어려워지고, 구성되지 않은 스파게티 코드를 홍보하고, 무언가를 깨지 않고 변경하기가 점점 어려워진다는 것입니다. 디자인 패턴을 사용하면 데이터를 더 잘 정리하는 데 도움이된다는 사실을 알게되었습니다. 어떤 상태에서 왔는지에 대한 상태 코드를 디자인하는 대신 새 상태에 들어갈 때 상태를 기록하도록 코드를 구성하십시오. 이렇게하면 이전 상태에 대한 기록을 효과적으로 얻을 수 있습니다. 나는 @JoshPetit의 대답을 좋아하고 GoF 책에서 바로 가져온 그의 솔루션을 한 단계 더 발전 시켰습니다.

stateCtxt.h :

#define STATE (void *)
typedef enum fsmSignal
{
   eEnter =0,
   eNormal,
   eExit
}FsmSignalT;

typedef struct fsm 
{
   FsmSignalT signal;
   // StateT is an enum that you can define any which way you want
   StateT currentState;
}FsmT;
extern int STATECTXT_Init(void);
/* optionally allow client context to set the target state */
extern STATECTXT_Set(StateT  stateID);
extern void STATECTXT_Handle(void *pvEvent);

stateCtxt.c :

#include "stateCtxt.h"
#include "statehandlers.h"

typedef STATE (*pfnStateT)(FsmSignalT signal, void *pvEvent);

static FsmT      fsm;
static pfnStateT UsbState ;

int STATECTXT_Init(void)
{    
    UsbState = State1;
    fsm.signal = eEnter;
    // use an enum for better maintainability
    fsm.currentState = '1';
    (*UsbState)( &fsm, pvEvent);
    return 0;
}

static void ChangeState( FsmT *pFsm, pfnStateT targetState )
{
    // Check to see if the state has changed
    if (targetState  != NULL)
    {
        // Call current state's exit event
        pFsm->signal = eExit;
        STATE dummyState = (*UsbState)( pFsm, pvEvent);

        // Update the State Machine structure
        UsbState = targetState ;

        // Call the new state's enter event
        pFsm->signal = eEnter;            
        dummyState = (*UsbState)( pFsm, pvEvent);
    }
}

void STATECTXT_Handle(void *pvEvent)
{
    pfnStateT newState;

    if (UsbState != NULL)
    {
         fsm.signal = eNormal;
         newState = (*UsbState)( &fsm, pvEvent );
         ChangeState( &fsm, newState );
    }        
}


void STATECTXT_Set(StateT  stateID)
{
     prevState = UsbState;
     switch (stateID) 
     {
         case '1':               
            ChangeState( State1 );
            break;
          case '2':
            ChangeState( State2);
            break;
          case '3':
            ChangeState( State3);
            break;
     }
}

statehandlers.h :

/* define state handlers */
extern STATE State1(void);
extern STATE State2(void);
extern STATE State3(void);

statehandlers.c :

#include "stateCtxt.h:"

/* Define behaviour to given set of inputs */
STATE State1(FsmT *fsm, void *pvEvent)
{   
    STATE nextState;
    /* do some state specific behaviours 
     * here
     */
    /* fsm->currentState currently contains the previous state
     * just before it gets updated, so you can implement behaviours 
     * which depend on previous state here
     */
    fsm->currentState = '1';
    /* Now, specify the next state
     * to transition to, or return null if you're still waiting for 
     * more stuff to process.  
     */
    switch (fsm->signal)
    {
        case eEnter:
            nextState = State2;
            break;
        case eNormal:
            nextState = null;
            break;
        case eExit:
            nextState = State2;
            break;
    }

    return nextState;
}

STATE  State3(FsmT *fsm, void *pvEvent)
{
    /* do some state specific behaviours 
     * here
     */
    fsm->currentState = '2';
    /* Now, specify the next state
     * to transition to
     */
     return State1;
}

STATE   State2(FsmT *fsm, void *pvEvent)
{   
    /* do some state specific behaviours 
     * here
     */
    fsm->currentState = '3';
    /* Now, specify the next state
     * to transition to
     */
     return State3;
}

대부분의 상태 머신의 경우 esp. 유한 상태 머신, 각 상태는 다음 상태가 무엇인지, 다음 상태로 전환하기위한 기준을 알고 있습니다. 느슨한 상태 설계의 경우에는 그렇지 않을 수 있으므로 상태 전환을 위해 API를 노출하는 옵션이 있습니다. 더 많은 추상화를 원하면 각 상태 핸들러를 자체 파일로 분리 할 수 ​​있습니다. 이는 GoF 책의 구체적인 상태 핸들러와 동일합니다. 디자인이 몇 개의 상태만으로 간단하다면 stateCtxt.c와 statehandlers.c는 단순성을 위해 단일 파일로 결합 될 수 있습니다.


State3 및 State2는 void로 선언 되었더라도 반환 값을 갖습니다.
개미

1

내 경험상 'switch'문을 사용하는 것이 여러 가능한 상태를 처리하는 표준 방법입니다. 나는 당신이 상태 별 처리에 전이 값을 전달하고 있다고 생각하지만. 상태 머신의 요점은 각 상태가 단일 작업을 수행한다는 것입니다. 그런 다음 다음 작업 / 입력에 따라 전환 할 새 상태가 결정됩니다. 따라서 각 상태 처리 기능이 상태에 들어가기 위해 고정 된 모든 것을 즉시 수행하고 나중에 다른 상태로 전환이 필요한지 여부를 결정하기를 기대했을 것입니다.


2
기본 모델에는 Mealy 머신과 Moore 머신이 있습니다. Mealy의 행동은 전환에 달려 있고, Moore의 행동은 국가에 달려 있습니다.
xmjx

1

C / C ++의 Practical Statecharts 라는 책이 있습니다 . 그러나, 그것은이다 방법 우리가 필요로하는 것을 너무 헤비급.


2
나는 책에 대해 똑같은 반응을 보였다. 꽤 직관적이고 간단하다고 생각되는 것을 설명하고 구현하는 데 700 개 이상의 페이지가 필요합니까?!?!?
Dan

1

를 지원하는 컴파일러의 __COUNTER__경우 단순 (그러나 큰) 상태 mashines에 사용할 수 있습니다.

  #define START 0      
  #define END 1000

  int run = 1;
  state = START;    
  while(run)
  {
    switch (state)
    {
        case __COUNTER__:
            //do something
            state++;
            break;
        case __COUNTER__:
            //do something
            if (input)
               state = END;
            else
               state++;
            break;
            .
            .
            .
        case __COUNTER__:
            //do something
            if (input)
               state = START;
            else
               state++;
            break;
        case __COUNTER__:
            //do something
            state++;
            break;
        case END:
            //do something
            run = 0;
            state = START;
            break;
        default:
            state++;
            break;
     } 
  } 

사용의 장점 __COUNTER__하드 코딩 된 숫자 대신 하는 매번 모든 번호를 다시 매기 지 않고 다른 상태의 중간에 상태를 추가 할 수 있다는 것입니다. 컴파일러 나던 지원하는 경우 __COUNTER__예방 조치와 함께 사용하는 제한된 방법으로, 그 추적 할 수없는 가망__LINE__


당신의 대답을 더 설명해 주시겠습니까?
abarisone

정상적인 "스위치"상태에서는 케이스 0, 케이스 1, 케이스 2, ... 케이스 100이 있습니다. 이제 5와 6 사이에 3 개의 케이스를 추가하려면 나머지를 100으로 다시 번호를 매겨 야합니다. 이제는 103이됩니다 __COUNTER__. 프리 컴파일러가 컴파일 중에 번호를 매기기 때문에를 사용하면 번호를 다시 매길 필요가 없습니다.
Seb

1

c에서 미니멀 UML 상태 머신 프레임 워크를 사용할 수 있습니다. https://github.com/kiishor/UML-State-Machine-in-C

유한 및 계층 적 상태 머신을 모두 지원합니다. 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;

프레임 워크는 dispatch_event이벤트를 상태 머신으로 전달 하는 API 와 상태 순회를위한 두 개의 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, const state_t*);

state_machine_result_t traverse_state(state_machine_t* const, const state_t*);

계층 적 상태 머신을 구현하는 방법에 대한 자세한 내용은 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


질문에 맞는 코드 예제를 추가 할 수 있습니까?
Giulio Caccin

1
저장소의 데모 폴더에는 한 가지 예가 있습니다. github.com/kiishor/UML-State-Machine-in-C/blob/master/demo/... . 나는 현재 키, LED 및 타이머를 포함하는 하나 이상의 임베디드 시스템 예제를 작업 중이지만 아직 완료되지 않았습니다. 준비되면 알려드립니다.
Nandkishor Biradar


0

귀하의 질문은 "일반적인 데이터베이스 구현 패턴이 있습니까"와 유사합니까? 대답은 무엇을 성취하고 싶은지에 달려 있습니다. 더 큰 결정 론적 상태 머신을 구현하려면 모델과 상태 머신 생성기를 사용할 수 있습니다. 예제는 www.StateSoft.org-SM Gallery에서 볼 수 있습니다. 야누스 도브로 볼 스키


당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.