C状态机设计 [英] C state-machine design
问题描述
我想知道你是否会在SO上分享你的状态机设计
注意:我主要是尝试了
UPDATED:根据SO上收集到的所有好的输入,我已经解决了这种架构:
之前设计的状态机(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(); (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;
}
}
}
}
As请注意,使用 ST_ANY
作为通配符,允许事件调用函数,而不管当前状态。 EV_ANY
也可以相似地工作,允许特定状态下的任何事件调用函数。
它也可以保证如果达到转换数组的结尾,则会收到一条错误,指出您的FSM尚未正确构建(使用 ST_ANY / EV_ANY
组合。
我已经在很多通信项目中使用了类似的代码,例如早期实现嵌入式系统的通信栈和协议,最大的优点是它的简单性和相对容易度更改过渡数组。
我毫无疑问会有更高层次的抽象,现在可能更适合,但我怀疑他们都会归结为同一个一种结构。
而且,由于 ldog
评论,你可以通过传递一个结构指针到所有的函数(并在事件循环中使用它)完全避免全局变量,这将允许多个stat e机器并排运行而不会受到干扰。
只需创建一个结构类型,它保存机器特定的数据(状态为最低限度),并使用它的全局变量。
我很少这样做的原因只是因为我写的大多数状态机都是单例类型(一次性,进程启动,配置文件读取等),不需要运行多个实例。但是,如果您需要运行多个数据,则它具有价值。
I am crafting a small project in mixed C and C++. I am building one small-ish state-machine at the heart of one of my worker thread.
I was wondering if you gurus on SO would share your state-machine design techniques.
NOTE: I am primarily after tried & tested implementation techniques.
UPDATED: Based on all the great input gathered on SO, I've settled on this architecture:
State machines that I've designed before (C, not C++) have all come down to a struct
array and a loop. The structure basically consists of a state and event (for look-up) and a function that returns the new state, something like:
typedef struct {
int st;
int ev;
int (*fn)(void);
} tTransition;
Then you define your states and events with simple defines (the ANY
ones are special markers, see below):
#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
Then you define all the functions that are called by the transitions:
static int GotKey (void) { ... };
static int FsmError (void) { ... };
All these function are written to take no variables and return the new state for the state machine. In this example global variables are used for passing any information into the state functions where necessary.
Using globals isn't as bad as it sounds since the FSM is usually locked up inside a single compilation unit and all variables are static to that unit (which is why I used quotes around "global" above - they're more shared within the FSM, than truly global). As with all globals, it requires care.
The transitions array then defines all possible transitions and the functions that get called for those transitions (including the catch-all last one):
tTransition trans[] = {
{ ST_INIT, EV_KEYPRESS, &GotKey},
: :
{ ST_ANY, EV_ANY, &FsmError}
};
#define TRANS_COUNT (sizeof(trans)/sizeof(*trans))
What that means is: if you're in the ST_INIT
state and you receive the EV_KEYPRESS
event, make a call to GotKey
.
The workings of the FSM then become a relatively simple loop:
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;
}
}
}
}
As alluded to above, note the use of ST_ANY
as wild-cards, allowing an event to call a function no matter the current state. EV_ANY
also works similarly, allowing any event at a specific state to call a function.
It can also guarantee that, if you reach the end of the transitions array, you get an error stating your FSM hasn't been built correctly (by using the ST_ANY/EV_ANY
combination.
I've used code similar for this on a great many communications projects, such as an early implementation of communications stacks and protocols for embedded systems. The big advantage was its simplicity and relative ease in changing the transitions array.
I've no doubt there will be higher-level abstractions which may be more suitable nowadays but I suspect they'll all boil down to this same sort of structure.
And, as ldog
states in a comment, you can avoid the globals altogether by passing a structure pointer to all functions (and using that in the event loop). This will allow multiple state machines to run side-by-side without interference.
Just create a structure type which holds the machine-specific data (state at a bare minimum) and use that instead of the globals.
The reason I've rarely done that is simply because most of the state machines I've written have been singleton types (one-off, at-process-start, configuration file reading for example), not needing to run more than one instance. But it has value if you need to run more than one.
这篇关于C状态机设计的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!