C state-machine 디자인 [폐쇄]

C와 C ++를 혼합하여 작은 프로젝트를 만들고 있습니다. 작업자 스레드 중 하나의 중심에 작은 상태의 기계 하나를 만들고 있습니다.

SO를 좋아하는 사람이 상태 머신 설계 기술을 공유 할 수 있을지 궁금합니다.

참고 : 나는 주로 구현 기술을 시험하고 테스트 한 후에 있습니다.

업데이트 : SO에 수집 된 모든 위대한 입력을 기반 으로이 아키텍처에 정착했습니다.

이벤트 펌프는 디스패처를 가리키는 이벤트 통합자를 가리 킵니다.  디스패처는 이벤트 통합자를 다시 가리키는 1-n 개의 조치를 가리 킵니다.  와일드 카드가 포함 된 전이 테이블은 디스패처를 가리 킵니다.



답변

내가 이전에 디자인 한 상태 머신 (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보다 진정한 글로벌). 모든 세계와 마찬가지로주의가 필요합니다.

그런 다음 전이 배열은 가능한 모든 전이 및 해당 전이에 대해 호출되는 기능 (포괄적 마지막 것 포함)을 정의합니다.

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특정 상태의 모든 이벤트가 함수를 호출 할 수 있도록 비슷하게 작동합니다.

또한 전환 배열의 끝에 도달하면 ST_ANY/EV_ANY조합 을 사용하여 FSM이 올바르게 작성되지 않았다는 오류가 발생합니다 .

임베디드 시스템의 통신 스택 및 프로토콜의 초기 구현과 같은 많은 통신 프로젝트에서 이와 유사한 코드를 사용했습니다. 가장 큰 장점은 전환 배열 변경의 단순성과 상대적으로 쉽다는 것입니다.

나는 오늘날 더 적합 할 수있는 더 높은 수준의 추상화가있을 것임은 의심 할 여지가 없지만, 이것들이 모두 같은 종류의 구조로 요약 될 것이라고 생각합니다.


또한 ldog주석의 상태와 같이 구조 포인터를 모든 함수에 전달하고 이벤트 루프에서 사용하면 전역을 피할 수 있습니다. 이를 통해 여러 상태 머신이 간섭없이 나란히 실행될 수 있습니다.

기계 별 데이터 (최소한 상태)를 보유하는 구조 유형을 작성하고 전역 대신 사용하십시오.

내가 거의 수행하지 않은 이유는 내가 작성한 대부분의 상태 머신이 단일 유형 (예 : 일회성, 프로세스 시작, 구성 파일 읽기)이기 때문에 둘 이상의 인스턴스를 실행할 필요가 없기 때문입니다. . 그러나 둘 이상을 실행해야하는 경우 가치가 있습니다.


답변

다른 답변은 좋지만 상태 머신이 매우 단순 할 때 사용한 매우 “경량”구현은 다음과 같습니다.

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

상태 머신이 함수 포인터 및 상태 전이 테이블 접근 방식이 지나치게 과도 할 정도로 간단 할 때 이것을 사용합니다. 이것은 종종 문자 별 또는 단어 별 구문 분석에 유용합니다.


답변

컴퓨터 과학의 모든 규칙을 어기는 것에 대해 용서해주십시오. 그러나 스테이트 머신은 goto성명서가 더 효율적일뿐만 아니라 코드를 더 깨끗하고 읽기 쉽게 만드는 몇 안되는 곳 중 하나입니다 . goto명령문은 레이블을 기반으로 하기 때문에 숫자의 혼란을 추적하거나 열거 형을 사용하지 않고 주 이름을 지정할 수 있습니다. 또한 함수 포인터 또는 거대한 스위치 문과 while 루프가 필요하지 않기 때문에 코드가 훨씬 깨끗합니다. 더 효율적이라고 언급 했습니까?

상태 머신은 다음과 같습니다.

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

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

당신은 일반적인 생각을 얻습니다. 요점은 효율적인 방식으로 상태 머신을 구현할 수 있으며 상태 머신을보고있는 독자에게 비교적 읽기 쉽고 비명을 지르는 것입니다. 당신이 goto진술 을 사용하는 경우 , 그렇게하는 동안 발로 자신을 쏘는 것이 매우 쉽기 때문에 여전히주의해야합니다.


답변

State Machine Compiler http://smc.sourceforge.net/을 고려할 수 있습니다 .

이 화려한 오픈 소스 유틸리티는 간단한 언어로 상태 머신에 대한 설명을 받아 C 및 C ++를 포함하여 12 개 정도의 언어 중 하나로 컴파일합니다. 유틸리티 자체는 Java로 작성되었으며 빌드의 일부로 포함될 수 있습니다.

이를 수행하는 이유는 GoF 상태 패턴 또는 다른 접근 방식을 사용하여 수동 코딩하는 대신 상태 머신이 코드로 표현되면 기본 구조가이를 지원하기 위해 생성해야하는 상용구의 무게에 따라 사라지는 경향이 있기 때문입니다. 이 접근 방식을 사용하면 여러 가지 우려 사항을 분리 할 수 ​​있으며 상태 머신의 구조를 ‘보이게’유지할 수 있습니다. 자동 생성 된 코드는 사용자가 직접 만질 필요가없는 모듈로 들어가므로 작성한 지원 코드에 영향을주지 않고 상태 시스템의 구조로 되돌아 갈 수 있습니다.

죄송합니다. 저는 너무 열정적이며 의심 할 여지없이 모든 사람을 미루고 있습니다. 그러나 최고 수준의 유틸리티이며 잘 문서화되어 있습니다.


답변

C / C ++ Users Journal의 기사 가 훌륭했던 Miro Samek (블로그 State Space , 웹 사이트 State Machines & Tools ) 의 작업을 확인하십시오 .

이 웹 사이트에는 상태 머신 프레임 워크 (QP 프레임 워크) , 이벤트 핸들러 (QEP) , 기본 모델링 도구 (QM)추적 도구 (QSpy)의 공개 소스 및 상업용 라이센스 모두에서 완전한 (C / C ++) 구현이 포함되어 있습니다. 상태 머신을 그리고 코드를 생성하고 디버깅 할 수 있습니다.

이 책은 구현의 이유 / 이유 및 사용 방법에 대한 광범위한 설명을 포함하며 계층 적 및 유한 상태 머신의 기본을 이해하는 데 유용한 자료입니다.

이 웹 사이트에는 내장 플랫폼과 함께 소프트웨어를 사용하기위한 여러 보드 지원 패키지에 대한 링크도 포함되어 있습니다.


답변

paxdiablo가 설명 한 것과 비슷한 것을 수행했습니다. 상태 / 이벤트 전환 배열 대신에만 이벤트 값을 한 축의 인덱스로 사용하고 현재 상태 값을 2 차원 함수 포인터 배열로 설정했습니다. 다른 사람. 그런 다음 그냥 전화 state = state_table[event][state](params)하면 올바른 일이 발생합니다. 유효하지 않은 상태 / 이벤트 조합을 나타내는 셀은 물론 그렇게 말하는 함수에 대한 포인터를 얻습니다.

분명히 이것은 상태 및 이벤트 값이 인접한 범위이고 0에서 시작하거나 충분히 가까운 경우에만 작동합니다.


답변

Stefan Heinzmann은 그의 기사 에서 매우 훌륭한 템플릿 기반 C ++ 상태 머신 “프레임 워크”를 제공합니다 .

이 기사에는 완전한 코드 다운로드 링크가 없으므로 코드를 프로젝트에 붙여넣고 확인하는 자유를 얻었습니다. 아래의 것들이 테스트되었으며 사소하지만 거의 명백한 누락 된 부분이 포함되어 있습니다.

여기서 중요한 혁신은 컴파일러가 매우 효율적인 코드를 생성한다는 것입니다. 빈 출입 통제 비용은 없습니다. 비어 있지 않은 입 / 출력 조치가 인라인됩니다. 컴파일러는 또한 상태 차트의 완전성을 확인합니다. 누락 된 조치는 링크 오류를 생성합니다. 잡히지 않는 유일한 것은 실종 Top::init입니다.

이것은 누락되지 않은 채 살 수 있다면 Miro Samek의 구현에 대한 훌륭한 대안입니다. 이것은 UML 의미를 올바르게 구현하지만 완전한 UML Statechart 구현과는 거리가 먼 반면, Samek의 코드는 종료 / 전환을 처리하지 않습니다 올바른 순서로 입력하십시오.

이 코드가 필요한 작업에 효과가 있고 시스템에 알맞은 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)