用 C 编写状态机的最佳方法是什么?
我通常在 for(;;) 中编写一个大的 switch-case 语句,并带有回调以在外部操作完成时重新进入状态机。
你知道更有效的方法吗?
9 回答
我喜欢Quantum Leaps方法。
当前状态是指向以事件对象为参数的函数的指针。当事件发生时,只需使用该事件调用状态函数;然后,只需将状态设置为另一个函数,该函数就可以完成它的工作并转换到另一个状态。
例如:
// 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 框架为诸如进入/退出/初始化操作、分层状态机等额外的东西提供了帮助程序。我强烈推荐这本书以获得更深入的解释和良好的实现。
最好的方法很大程度上是主观的,但一种常见的方法是使用“基于表”的方法,将状态代码(枚举或其他整数类型)映射到函数指针。该函数返回您的下一个状态和其他相关数据,然后您循环直到达到最终状态。这实际上可能就是您在上面描述的方法。
这几乎是标准方法。如果您有兴趣研究经过深思熟虑的图书馆并比较细节,请查看Ragel:
Ragel 从常规语言编译可执行的有限状态机。Ragel 针对 C、C++、Objective-C、D、Java 和 Ruby。Ragel 状态机不仅可以像正则表达式机器那样识别字节序列,而且还可以在识别正则语言的任意点执行代码。代码嵌入是使用不破坏常规语言语法的内联运算符完成的。
另一种方法是 2D 数组,它为每个状态/事件组合描述要执行的动作和要进入的下一个状态。当您需要根据“情况”转换到不同的状态时,这可能会变得更难管理,但它可以很好地工作。您有一个事件识别器函数,它返回下一个事件;您有一个表,其中表中的每个条目都标识了接收事件时要调用的函数以及要进入的下一个状态——除非被调用的函数覆盖了该状态。
实际上生成这样的代码更复杂——这首先取决于 FSM 的描述方式。发现重复的动作通常很重要。通常,您可以依赖不明确记录错误处理的“稀疏矩阵”技术:如果条目在逻辑上存在于稀疏矩阵中,则您对该事件/状态信息采取行动,但如果该条目不存在,您将退回到适当的错误报告和重新同步代码。
指向结构的 2D 指针数组可以传递给通用 FSM 函数;您编写三指针的事实足以使您对正在发生的事情保持谨慎。(我在 1986 年 3 月写了其中一个 - 我不再在磁盘上找到它的来源,尽管我仍然有描述它的文档的打印输出。)
我使用了这种模式。有没有典型的状态机实现模式?(检查最佳答案)。
但我还添加了一些功能
1. 有关先前状态的信息。
2.参数传递
3.添加全局超时和“重置SM”等外部事件
我发现状态机不那么神秘和可维护。
无论如何,我仍然认为状态机是最困难和最烦人的编程任务。(到目前为止)
看看这里:http ://code.google.com/p/fwprofile/
它是用 C 语言实现的状态机的开源版本 (GNU GPLv3)。其概念和实现非常适合用于任务关键型应用程序。在工业应用中有部署。
我使用函数指针和二维查找表,其中一个参数使用状态,另一个参数使用事件。
我使用 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
}
这种方法本质上迫使开发人员考虑每种状态下所有可能的事件,并且以我的经验使调试更容易一些。
您可以使用用 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;
该框架提供了一个 APIdispatch_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项目。