用C编写状态机的最佳方法是什么?
我通常在for(;;)中写一个大的switch-case语句,并在外部操作完成时使用回调来重新进入状态机。
你知道一种更有效的方法吗?
答案 0 :(得分:26)
我喜欢Quantum Leaps方法。
当前状态是指向将事件对象作为参数的函数的指针。当事件发生时,只需使用该事件调用state函数;然后,该函数可以通过将状态设置为另一个函数来完成其工作并转换到另一个状态。
E.g:
// State type and variable, notice that it's 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框架为进入/退出/初始化操作,分层状态机等额外事务提供帮助。我强烈建议the book进行更深入的解释和良好的实现。
答案 1 :(得分:6)
最好的方法主要是主观的,但常用的方法是使用“基于表格”的方法,将状态代码(枚举或其他整数类型)映射到函数指针。该函数返回您的下一个状态和其他相关数据,然后循环执行此操作,直到达到终端状态。事实上,这可能就是您所描述的上述方法。
答案 2 :(得分:4)
这几乎是标准方法。如果您有兴趣学习一个考虑周全的图书馆并比较具体细节,请查看Ragel:
Ragel从常规语言编译可执行的有限状态机。 Ragel的目标是C,C ++,Objective-C,D,Java和Ruby。 Ragel状态机不仅可以像正则表达式机器一样识别字节序列,还可以在识别常规语言的任意点处执行代码。代码嵌入是使用不会破坏常规语言语法的内联运算符完成的。
答案 3 :(得分:3)
Switch语句是一种很好的入门方式,但是当FSM变大时,它们往往变得难以处理。
一些相关(或重复)的SO问题,包含很多信息和想法:
答案 4 :(得分:1)
另一种方法是2D数组,它为每个状态/事件组合描述要执行的操作和要进入的下一个状态。当您需要根据“情况”转换到不同的状态时,管理起来会变得更加棘手,但可以使其运行良好。你有一个事件识别器功能,它返回下一个事件;你有一个表,其中表中的每个条目都标识了在接收事件时要调用的函数以及要转到的下一个状态 - 除非被调用的函数覆盖该状态。
实际上生成这样的代码比较狡猾 - 这取决于首先描述FSM的方式。发现重复操作通常很重要。通常,您可以依赖于不显式记录错误处理的“稀疏矩阵”技术:如果条目逻辑上存在于稀疏矩阵中,您将对该事件/状态信息进行操作,但如果该条目不存在,则会回退到适当的错误报告和重新同步代码。
可以将结构指针的2D数组传递给通用FSM函数;你写一个三指针的事实足以让你对发生的事情保持谨慎。 (我在1986年3月写过其中一篇 - 我不再在磁盘上找到它的来源了,尽管我仍然打印出描述它的文档。)
答案 5 :(得分:1)
我用过这种模式。 Is there a typical state machine implementation pattern?(查看最佳答案)。
但我也添加了一些功能
1.有关以前州的信息
2.参数传递
3.添加全局超时和“重新发布SM”等外部事件
我发现状态机不那么神秘和可维护 无论如何,我仍然认为状态机是最困难和最烦人的编程任务。(我到目前为止)
答案 6 :(得分:0)
看看这里:http://code.google.com/p/fwprofile/
它是实现的状态机的开源版本(GNU GPLv3) 在C.概念和实施非常适合用于 任务关键型应用程序。有工业部署 应用
答案 7 :(得分:0)
我使用函数指针和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
}
这种方法实质上迫使开发人员考虑每个状态中的所有可能事件,根据我的经验,调试更容易一些。
答案 8 :(得分:0)
您可以使用c中实现的极简uml-state-machine框架。它支持有限状态机和分层状态机。该框架非常简约。它只有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项目。