C State-Design - PullRequest
       95

C State-Design

191 голосов
/ 30 октября 2009

Я занимаюсь созданием небольшого проекта на смешанных C и C ++. Я создаю один конечный автомат с маленьким размером в сердце одного из моих рабочих потоков.

Мне было интересно, если бы вы, гуру из 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.

Ответы [ 25 ]

168 голосов
/ 30 октября 2009

Конечные автоматы, которые я проектировал ранее (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 обычно блокируется внутри одного модуля компиляции, и все переменные являются статическими по отношению к этому модулю (вот почему я использовал кавычки вокруг «глобального» выше - они более поделился внутри ФСМ, чем по-настоящему глобальный). Как и все глобальные, он требует осторожности.

Затем массив переходов определяет все возможные переходы и функции, которые вызываются для этих переходов (включая последний из них):

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 также работает аналогично, позволяя любому событию в определенном состоянии вызывать функцию.

Это также может гарантировать, что, если вы достигнете конца массива переходов, вы получите сообщение о том, что ваш FSM не был построен правильно (с помощью комбинации ST_ANY/EV_ANY.

Я использовал подобный код для очень многих коммуникационных проектов, таких как ранняя реализация стеков связи и протоколов для встроенных систем. Большим преимуществом была его простота и относительная простота в изменении массива переходов.

Я не сомневаюсь, что будут абстракции более высокого уровня, которые могут быть более подходящими в наше время, но я подозреваю, что все они будут сводиться к такой же структуре.


И, как указывает ldog в комментарии, вы можете вообще избежать глобальных переменных, передавая структурный указатель всем функциям (и используя его в цикле событий). Это позволит нескольким конечным автоматам работать без помех.

Просто создайте тип структуры, который содержит машинно-зависимые данные (состояние как минимум) и используйте их вместо глобальных.

Причина, по которой я редко это делал, заключается в том, что большинство машин состояний, которые я написал, были одноэлементными (например, одноразовые, при запуске процесса, чтение файла конфигурации), и не нужно больше запускать чем один экземпляр. Но это имеет значение, если вам нужно запустить более одного.

78 голосов
/ 30 октября 2009

Остальные ответы хорошие, но очень «легковесная» реализация, которую я использовал, когда конечный автомат очень прост, выглядит так:

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

Я бы использовал это, когда конечный автомат достаточно прост, чтобы подход указателя функции и таблицы перехода состояний был излишним. Это часто полезно для посимвольного или пословного разбора.

34 голосов
/ 31 октября 2009

Прошу прощения за нарушение всех правил в области компьютерных наук, но конечный автомат - одно из немногих (я могу считать только два) места, где оператор goto не только более эффективен, но и делает ваш код чище и легче читать. Поскольку операторы goto основаны на метках, вы можете называть свои состояния вместо того, чтобы отслеживать беспорядок чисел или использовать enum. Это также делает код намного чище, так как вам не нужны все лишние указатели функций или огромные операторы 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, вы все равно должны быть осторожны, так как при этом очень легко выстрелить себе в ногу.

29 голосов
/ 30 октября 2009

Вы можете рассмотреть State Machine Compiler http://smc.sourceforge.net/

Эта великолепная утилита с открытым исходным кодом принимает описание конечного автомата на простом языке и компилирует его на любой из дюжины или около того языков, включая C и C ++. Сама утилита написана на Java и может быть включена как часть сборки.

Причина, по которой это делается, а не ручное кодирование с использованием шаблона состояния GoF или любого другого подхода, заключается в том, что после того, как ваш конечный автомат выражается в виде кода, базовая структура имеет тенденцию исчезать под весом стандартного шаблона, который необходимо сгенерировать поддержать это. Использование этого подхода дает вам отличное разделение задач, и вы сохраняете структуру своего конечного автомата «видимой». Автоматически сгенерированный код входит в модули, к которым вам не нужно прикасаться, чтобы вы могли вернуться и поиграть со структурой конечного автомата, не влияя на написанный вами вспомогательный код.

Извините, я преувеличиваю и, несомненно, откладываю всех. Но это первоклассная утилита, и хорошо документированная тоже.

20 голосов
/ 30 октября 2009

Обязательно проверьте работу Миро Самека (блог State Space , веб-сайт State Machines & Tools ), чьи статьи в C / C ++ Users Journal были великолепны.

Веб-сайт содержит полную (C / C ++) реализацию как с открытым исходным кодом, так и с коммерческой лицензией платформы конечного автомата (QP Framework) , обработчика событий (QEP) , базовый инструмент моделирования (QM) и инструмент трассировки (QSpy) , которые позволяют рисовать конечные автоматы, создавать код и отлаживать их.

Книга содержит подробное объяснение того, что / почему для реализации и как ее использовать, а также является отличным материалом для понимания основ иерархических и конечных автоматов.

Сайт также содержит ссылки на несколько пакетов поддержки плат для использования программного обеспечения со встроенными платформами.

11 голосов
/ 30 октября 2009

Я сделал нечто похожее на то, что описывает paxdiablo, только вместо массива переходов между состояниями и событиями я установил двумерный массив указателей функций со значением события в качестве индекса одной оси и текущего Государственное значение как другое. Тогда я просто звоню state = state_table[event][state](params) и все происходит правильно. Ячейки, представляющие недопустимые комбинации состояния / события, получают указатель на функцию, которая говорит об этом, конечно.

Очевидно, что это работает, только если значения состояния и события являются смежными диапазонами и начинаются с 0 или достаточно близко.

9 голосов
/ 22 февраля 2013

Стефан Хайнцманн (Stefan Heinzmann) в своей статье 1002 *.

предлагает очень красивую "структуру" конечного автомата на основе шаблонов.

Поскольку в статье нет ссылки на полную загрузку кода, я позволил себе вставить код в проект и проверить его. Материал ниже тестируется и включает в себя несколько незначительных, но довольно очевидных недостающих частей.

Основным нововведением здесь является то, что компилятор генерирует очень эффективный код. Пустые действия входа / выхода бесплатны. Непустые действия входа / выхода встроены. Компилятор также проверяет полноту диаграммы состояний. Пропущенные действия приводят к ошибкам связывания Единственное, что не поймано - это пропавшие Top::init.

Это очень хорошая альтернатива реализации Миро Самека, если вы можете жить без того, чего не хватает - это далеко от полной реализации UML Statechart, хотя она правильно реализует семантику UML, тогда как код Самека по дизайну не обрабатывает действия выхода / перехода / входа в правильном порядке.

Если этот код работает для того, что вам нужно, и у вас есть достойный компилятор 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)
5 голосов
/ 25 декабря 2009

Простейший случай

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

Очки: Состояние является частным не только для модуля компиляции, но и для обработчика события. Особые случаи могут обрабатываться отдельно от главного коммутатора с использованием любой конструкции, которая будет сочтена необходимой.

Более сложный случай

Когда коммутатор заполнится больше, чем на пару экранов, разделите его на функции, которые обрабатывают каждое состояние, используя таблицу состояний для непосредственного поиска функции. Состояние по-прежнему закрыто для обработчика событий. Функции обработчика состояния возвращают следующее состояние. При необходимости некоторые события могут получить специальную обработку в основном обработчике событий. Мне нравится добавлять псевдо-события для входа и выхода из состояния и, возможно, запуска конечного автомата:

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

Я не уверен, прибил ли я синтаксис, особенно в отношении массива указателей на функции. Я не запускал ничего из этого через компилятор. После проверки я заметил, что забыл явно отбрасывать следующее состояние при обработке псевдо-событий (скобка (void) перед вызовом state_handler ()). Это то, что мне нравится делать, даже если компиляторы молча принимают это упущение. Он сообщает читателям кода, что «да, я действительно имел в виду вызов функции без использования возвращаемого значения», и это может помешать инструментам статического анализа предупреждать об этом. Это может быть своеобразным, потому что я не помню, чтобы кто-нибудь еще делал это.

Точки: добавление крошечной сложности (проверка, отличается ли следующее состояние от текущего), может избежать дублирования кода в другом месте, потому что функции обработчика состояний могут наслаждаться псевдо-событиями, которые происходят при входе и выходе из состояния. Помните, что состояние не может измениться при обработке псевдо-событий, потому что результат обработчика состояния отбрасывается после этих событий. Вы, конечно, можете изменить поведение.

Обработчик состояния будет выглядеть так:

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 строк), поместите каждый обработчик состояний в отдельный файл. Когда каждый обработчик состояния становится длиннее пары экранов, каждое событие разделяется на отдельную функцию, аналогично тому, как был разделен переключатель состояния. Вы можете сделать это несколькими способами, отдельно от государства или с помощью общей таблицы, или комбинируя различные схемы. Некоторые из них были освещены здесь другими. Сортируйте таблицы и используйте бинарный поиск, если требуется скорость.

Общее программирование

Мне бы хотелось, чтобы препроцессор имел дело с такими проблемами, как сортировка таблиц или даже создание конечных автоматов из описаний, позволяющих вам «писать программы о программах». Я верю, что это то, для чего люди Boost используют шаблоны C ++, но я нахожу синтаксис загадочным.

Двумерные таблицы

В прошлом я использовал таблицы состояний / событий, но должен сказать, что для простейших случаев я не нахожу их необходимыми и предпочитаю ясность и удобочитаемость оператора switch, даже если он расширяется за один полный экран. В более сложных случаях таблицы быстро выходят из-под контроля, как отмечали другие. Приводимые здесь идиомы позволяют вам добавлять массу событий и состояний, когда вам это нравится, без необходимости поддерживать таблицу, потребляющую память (даже если это может быть память программы).

Отказ

Особые потребности могут сделать эти идиомы менее полезными, но я обнаружил, что они очень четкие и понятные.

5 голосов
/ 23 ноября 2009

Техника, которая мне нравится для конечных автоматов (по крайней мере, для управления программой), заключается в использовании указателей на функции. Каждое состояние представлено другой функцией. Функция принимает входной символ и возвращает указатель функции для следующего состояния. Мониторы центрального диспетчерского контура принимают следующий вход, подают его в текущее состояние и обрабатывают результат.

Печатание на нем становится немного странным, поскольку 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);
    }
 }

Тогда ваши отдельные функции состояния могут включить их вход для обработки и вернуть соответствующее значение.

4 голосов
/ 31 октября 2009

Чрезвычайно непроверенный, но забавный код, теперь в более изысканной версии, чем мой первоначальный ответ; последние версии можно найти по адресу 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;
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...