C状态机设计 [英] C state-machine design

查看:362
本文介绍了C状态机设计的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在以混合C和C ++编写一个小项目。我正在我的一个工作线程的核心上构建一个小型国家机器。



我想知道你是否会在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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆