Государственные машины в C
Каков наилучший способ записи конечного автомата в C?
Обычно я пишу большой оператор switch-case в for (;;), с обратными вызовами для повторного входа в конечный автомат, когда внешняя операция завершена.
Знаете ли вы более эффективный способ?
Ответы
Ответ 1
Мне нравится подход Quantum Leaps.
Текущее состояние является указателем на функцию, которая принимает объект события в качестве аргумента. Когда происходит событие, просто вызовите функцию состояния с этим событием; Затем функция может выполнить свою работу и перейти в другое состояние, просто установив состояние в другую функцию.
например:.
// State type and variable, notice that it 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 предоставляют помощники для дополнительных вещей, таких как операции ввода/выхода/init, иерархические государственные машины и т.д. Я настоятельно рекомендую книгу для более глубокое объяснение и хорошая реализация этого.
Ответ 2
Лучший способ в значительной степени субъективен, но распространенным способом является использование "основанного на таблице" подхода, когда вы указываете коды состояний (перечисления или какой-либо другой интегральный тип) на указатели функций. Функция возвращает ваше следующее состояние и другие связанные данные, и вы выполняете цикл до тех пор, пока не будет достигнуто состояние терминала. На самом деле это может быть то, что вы описываете как ваш подход выше.
Ответ 3
Это в значительной степени стандартный подход. Если вы заинтересованы в изучении хорошо продуманной библиотеки и сравнении ее особенностей, посмотрите Ragel:
Ragel компилирует исполняемые конечные машины с регулярными языками. Ragel нацелен на C, С++, Objective-C, D, Java и Ruby. Ранговые машины Ragel могут не только распознавать последовательности байтов, как машины с регулярными выражениями, но также могут выполнять код в произвольных точках при распознавании обычного языка. Встраивание кода выполняется с использованием встроенных операторов, которые не нарушают синтаксис регулярного языка.
Ответ 4
Операторы Switch - хороший способ начать работу, но они, как правило, становятся громоздкими, когда FSM становится больше.
Пара, связанная (или повторяющая) SO-вопросы с большой информацией и идеями:
Ответ 5
Альтернативный подход - это 2D-массив, который описывает для каждой комбинации состояний/событий действия для выполнения и следующее состояние. Это может стать более сложным для управления, когда вам нужно перейти в разные штаты в зависимости от "обстоятельств", но это может быть сделано для того, чтобы хорошо работать. У вас есть функция распознавания событий, которая возвращает следующее событие; у вас есть таблица, в которой каждая запись в таблице идентифицирует функцию для вызова при получении события и следующего состояния для перехода к - если вызываемая функция не отменяет это состояние.
На самом деле генерация такого кода является fiddlier - это зависит от того, как описывается FSM в первую очередь. Часто важно дублировать повторяющиеся действия. Часто вы можете полагаться на методы "разреженной матрицы", которые явно не фиксируют обработку ошибок: если запись логически существует в разреженной матрице, вы действуете на эту информацию о событии/состоянии, но если запись не существует, вы возвращаетесь к соответствующим отчет об ошибках и код повторной синхронизации.
2D-массив указателей на структуры может быть передан в общую функцию FSM; тот факт, что вы пишете тройной указатель, достаточно, чтобы вы были осторожны в том, что происходит. (Я написал один из них еще в марте 1986 года - у меня больше нет источника для этого на диске, хотя у меня все еще есть распечатка документа, который его описал.)
Ответ 6
Я использовал этот шаблон. Существует ли типичный шаблон реализации государственного аппарата? (проверьте лучший ответ).
Но я также добавляю некоторые функции
1. Информация о предыдущем состоянии.
2. Передача параметров
3. Добавление внешних событий, таких как глобальный тайм-аут и "переопределение SM"
Я нашел государственные машины немного менее загадочными и поддерживаемыми.
Во всяком случае, я все еще думаю, что государственные машины - это самая сложная и раздражающая задача программирования (я до сих пор).
Ответ 7
Посмотрите здесь: http://code.google.com/p/fwprofile/
Это версия с открытым исходным кодом (GNU GPLv3), реализованная на государственной машине
в C. Концепция и реализация хорошо подходят для использования в
критически важных приложений. Существуют развертывания в промышленных
приложения.
Ответ 8
Я использую указатели функций и таблицу поиска 2d, где я использую состояние для одного параметра, а событие - как другое.
Я использую excel (или любой инструмент для работы с электронными таблицами) для сопоставления функции с каждой комбинацией состояний/событий.
Когда происходит событие, я помещаю его в очередь, поэтому у меня есть что-то похожее на это
int main(void)
{
StateList currentState = start_up;
EventList currentEvent;
uint8_t stateArray[STATE_COUNT][EVENT_COUNT];
InitializeStateArray(stateArray);
InitializeEventQue();
while(1)
{
currentEvent = GetPriorityEvent();
currentState = (StateList)(*(stateArray[currentState][currentEvent]))();
}
return 1; //should never get here
}
Этот метод существенно заставляет разработчика учитывать все возможные события в каждом состоянии, и по моему опыту упрощает отладку.
Ответ 9
Вы можете использовать минималистский фреймворк uml-state-machine, реализованный в 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;
Если инфраструктура настроена для поддержки иерархического конечного автомата. Он содержит три дополнительных члена для представления иерархических отношений между состояниями.
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;
Инфраструктура предоставляет API dispatch_event
для отправки события на конечный автомат и два 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.