C状态机设计

我正在制作一个混合C和c++的小项目。我正在一个工作线程的中心构建一个小型的状态机。

我想知道你们这些SO方面的大师是否愿意分享你们的状态机设计技术。

注意:我主要是在尝试&经过测试的实现技术。

更新:基于SO上收集的所有伟大的输入,我已经确定了这个架构:

事件泵指向指向调度程序的事件集成器。调度程序指向1到n个动作,这些动作又指向事件积分器。带有通配符的转换表指向调度程序。 < / >

98857 次浏览
你的问题很笼统,
这里有两篇可能有用的参考文章,

  1. < p > 嵌入式状态机实现

    本文描述了一种为嵌入式系统实现状态机的简单方法。在本文中,状态机被定义为可以处于少数状态之一的算法。状态是导致输入到输出以及输入到下一个状态之间的规定关系的一种条件 聪明的读者很快就会注意到本文中描述的状态机是Mealy机。Mealy机是一种状态机,其输出是当前状态和输入的函数,而Moore机则相反,其输出只是状态的函数
    • 在C和c++中编码状态机

      在本文中,我主要关注的是状态机基础知识和一些用C或c++编写状态机的简单编程准则。我希望这些简单的技术可以变得更加普遍,这样您(和其他人)就可以很容易地从源代码中看到状态机结构。

      李< /引用> < / > 李< / ul > < / >

这个关于有点复杂的控制逻辑的Ars OpenForum系列帖子包含一个非常易于遵循的C语言状态机实现。

我之前设计的状态机(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通常被锁在一个编译单元中,所有变量对该单元来说都是静态的(这就是为什么我在上面的“global”周围使用引号——它们在FSM内部更共享,而不是真正的全局)。与所有全局变量一样,它需要小心。

然后,transitions数组定义了所有可能的转换以及为这些转换调用的函数(包括最后一个catch-all):

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();
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;
}
}
}
}

如上所述,注意使用ST_ANY作为通配符,允许事件无论当前状态如何都可以调用函数。EV_ANY也有类似的工作方式,允许任何处于特定状态的事件调用函数。

它还可以保证,如果你到达过渡数组的末尾,你会得到一个错误,说明你的FSM没有正确构建(通过使用ST_ANY/EV_ANY组合)。

我在许多通信项目中使用过类似的代码,比如用于嵌入式系统的通信堆栈和协议的早期实现。最大的优势是它的简单性和在改变过渡数组时相对容易。

我毫不怀疑将来会有更适合现在的更高层次的抽象,但我怀疑它们都将归结为同一种结构。


并且,正如ldog在注释中所述,可以通过向所有函数传递结构指针(并在事件循环中使用它)来完全避免全局变量。这将允许多个状态机并排运行而不受干扰。

只需创建一个结构类型来保存特定于机器的数据(最少的状态),并使用该结构类型来代替全局变量。

我很少这样做的原因很简单,因为我所编写的大多数状态机都是单例类型(例如,一次性的、在进程启动时、读取配置文件),不需要运行多个实例。但如果需要运行多个程序,它就有价值了。

其他答案都很好,但当状态机非常简单时,我使用了一个非常“轻量级”的实现,如下所示:

enum state { ST_NEW, ST_OPEN, ST_SHIFT, ST_END };


enum state current_state = ST_NEW;


while (current_state != ST_END)
{
input = get_input();


switch (current_state)
{
case ST_NEW:
/* Do something with input and set current_state */
break;


case ST_OPEN:
/* Do something different and set current_state */
break;


/* ... etc ... */
}
}

当状态机足够简单,函数指针&状态转换表的方法是多余的。这对于逐字符或逐字解析通常很有用。

一定要看看Miro Samek(博客状态空间,网站状态机;工具)的作品,他在C/ c++用户日志上的文章很棒。

该网站包含了一个完整的(C/ c++)实现,包括开源和商业许可的状态机框架(QP框架)事件处理器(QEP)基本建模工具跟踪工具(QSpy),允许绘制状态机,创建代码和调试它们。

这本书包含了对实现的内容/原因以及如何使用它的广泛解释,也是理解层次和有限状态机的基础知识的很好的材料。

该网站还包含几个板支持包的链接,用于在嵌入式平台上使用该软件。

我所做的事情与paxdiablo所描述的类似,只是我没有设置一个状态/事件转换数组,而是设置了一个2维函数指针数组,其中一个轴的索引是事件值,另一个轴是当前状态值。然后我调用state = state_table[event][state](params),正确的事情发生了。当然,表示无效状态/事件组合的单元格会得到一个指向这样表示的函数的指针。

显然,只有当状态值和事件值都是连续的范围并且从0开始或足够接近时,这才有效。

你可以考虑使用状态机编译器http://smc.sourceforge.net/

这个出色的开源实用程序接受一种简单语言的状态机描述,并将其编译为十几种语言中的任何一种——包括C和c++。该实用程序本身是用Java编写的,可以作为构建的一部分包含在内。

这样做的原因,而不是使用GoF状态模式或任何其他方法手工编码,是因为一旦您的状态机被表示为代码,底层结构就会在需要生成来支持它的样板文件的重压下消失。使用这种方法可以很好地分离关注点,并且保持状态机的结构“可见”。自动生成的代码放入您不需要接触的模块中,这样您就可以返回并修改状态机的结构,而不会影响您所编写的支持代码。

对不起,我太热情了,无疑把大家都耽误了。但它是一个一流的实用程序,而且文档也很详尽。

非常未经测试,但编码很有趣,现在的版本比我最初的答案更精致;最新版本可以在mercurial.intuxication.org找到:

sm.h

#ifndef SM_ARGS
#error "SM_ARGS undefined: " \
"use '#define SM_ARGS (void)' to get an empty argument list"
#endif


#ifndef SM_STATES
#error "SM_STATES undefined: " \
"you must provide a list of comma-separated states"
#endif


typedef void (*sm_state) SM_ARGS;
static const sm_state SM_STATES;


#define sm_transit(STATE) ((sm_state (*) SM_ARGS)STATE)


#define sm_def(NAME) \
static sm_state NAME ## _fn SM_ARGS; \
static const sm_state NAME = (sm_state)NAME ## _fn; \
static sm_state NAME ## _fn SM_ARGS

example.c

#include <stdio.h>


#define SM_ARGS (int i)
#define SM_STATES EVEN, ODD
#include "sm.h"


sm_def(EVEN)
{
printf("even %i\n", i);
return ODD;
}


sm_def(ODD)
{
printf("odd  %i\n", i);
return EVEN;
}


int main(void)
{
int i = 0;
sm_state state = EVEN;


for(; i < 10; ++i)
state = sm_transit(state)(i);


return 0;
}

在哪儿见过

#define FSM
#define STATE(x)      s_##x :
#define NEXTSTATE(x)  goto s_##x


FSM {
STATE(x) {
...
NEXTSTATE(y);
}


STATE(y) {
...
if (x == 0)
NEXTSTATE(y);
else
NEXTSTATE(x);
}
}

请原谅我打破了计算机科学中的每一条规则,但状态机是为数不多的(我可以立即数出两个)goto语句不仅更有效,而且还使你的代码更干净,更容易阅读的地方之一。因为goto语句是基于标签的,所以你可以命名你的状态,而不必跟踪一堆乱七八糟的数字或使用枚举。它还使代码更加简洁,因为您不需要所有额外的函数指针或巨大的switch语句和while循环。我说过它也更有效率吗?

下面是状态机的样子:

void state_machine() {
first_state:
// Do some stuff here
switch(some_var) {
case 0:
goto first_state;
case 1:
goto second_state;
default:
return;
}


second_state:
// Do some stuff here
switch(some_var) {
case 0:
goto first_state;
case 1:
goto second_state;
default:
return;
}
}

你知道大概的意思了。关键在于,您可以以一种有效的方式实现状态机,而且这种方式相对容易阅读,并让读者知道他们正在查看的是状态机。注意,如果你使用goto语句,你仍然必须小心,因为在这样做的时候很容易搬起石头砸自己的脚。

对于状态机(至少对于程序控制而言),我喜欢的技术是使用函数指针。每种状态由不同的函数表示。该函数接受一个输入符号,并返回指向下一个状态的函数指针。中央调度循环监控器接收下一个输入,将其提供给当前状态,并处理结果。

它的类型有点奇怪,因为C语言没有办法指出函数指针返回自身的类型,所以状态函数返回void*。但是你可以这样做:

typedef void* (*state_handler)(input_symbol_t);
void dispatch_fsm()
{
state_handler current = initial_handler;
/* Let's assume returning null indicates end-of-machine */
while (current) {
current = current(get_input);
}
}

然后,您的各个状态函数可以打开它们的输入来处理并返回适当的值。

到这么晚(像往常一样),但扫描到目前为止的答案,我认为一些重要的东西缺失了;

我在自己的项目中发现,为每个有效的状态/事件组合提供一个函数是非常有用的。我确实喜欢有效地拥有一个2D状态/事件表的想法。但我希望表元素不仅仅是一个简单的函数指针。相反,我试图组织我的设计,所以它的核心是由一堆简单的原子元素或动作组成。这样我就可以在状态/事件表的每个交叉点上列出这些简单的原子元素。这个想法是你必须定义大量的N平方(通常非常简单)函数。为什么要有这么容易出错、耗时、难写、难读的东西,你能想到的都有?

我还为表中的每个单元格添加了一个可选的new状态和一个可选的函数指针。函数指针是为那些不希望仅仅触发原子操作列表的特殊情况而存在的。

当您仅仅通过编辑表就可以表达许多不同的功能,而不需要编写新代码时,您就知道这样做是正确的。

我已经在Java和Python项目中成功地使用了状态机编译器

简单的情况

enum event_type { ET_THIS, ET_THAT };
union event_parm { uint8_t this; uint16_t that; }
static void handle_event(enum event_type event, union event_parm parm)
{
static enum { THIS, THAT } state;
switch (state)
{
case THIS:
switch (event)
{
case ET_THIS:
// Handle event.
break;


default:
// Unhandled events in this state.
break;
}
break;


case THAT:
// Handle state.
break;
}
}
< p >点: State是私有的,不仅对编译单元私有,对event_handler也是私有的。 特殊情况可以与主开关分开处理,使用任何认为必要的构造

更复杂的情况

当开关的大小超过两个屏幕时,将其拆分为处理每个状态的函数,使用状态表直接查找该函数。状态仍然是事件处理程序私有的。状态处理程序函数返回下一个状态。如果需要,一些事件仍然可以在主事件处理程序中接受特殊处理。我喜欢为状态进入和退出以及状态机启动添加伪事件:

enum state_type { THIS, THAT, FOO, NA };
enum event_type { ET_START, ET_ENTER, ET_EXIT, ET_THIS, ET_THAT, ET_WHATEVER, ET_TIMEOUT };
union event_parm { uint8_t this; uint16_t that; };
static void handle_event(enum event_type event, union event_parm parm)
{
static enum state_type state;
static void (* const state_handler[])(enum event_type event, union event_parm parm) = { handle_this, handle_that };
enum state_type next_state = state_handler[state](event, parm);
if (NA != next_state && state != next_state)
{
(void)state_handler[state](ET_EXIT, 0);
state = next_state;
(void)state_handler[state](ET_ENTER, 0);
}
}

我不确定我是否掌握了语法,特别是关于函数指针的数组。我没有通过编译器运行这些内容。经过回顾,我注意到在处理伪事件(调用state_handler()之前的(void)括号)时忘记显式地丢弃下一个状态。这是我喜欢做的事情,即使编译器默默地接受省略。它告诉代码的读者“是的,我确实想调用函数而不使用返回值”,并且它可能会阻止静态分析工具对此发出警告。这可能有些特殊,因为我不记得有其他人这么做过。

要点:添加一点点复杂性(检查下一个状态是否与当前状态不同),可以避免在其他地方重复代码,因为状态处理程序函数可以享受进入和离开状态时发生的伪事件。请记住,在处理伪事件时状态不能更改,因为在这些事件发生后,状态处理程序的结果将被丢弃。当然,您可以选择修改行为。

状态处理程序看起来像这样:

static enum state_type handle_this(enum event_type event, union event_parm parm)
{
enum state_type next_state = NA;
switch (event)
{
case ET_ENTER:
// Start a timer to do whatever.
// Do other stuff necessary when entering this state.
break;


case ET_WHATEVER:
// Switch state.
next_state = THAT;
break;


case ET_TIMEOUT:
// Switch state.
next_state = FOO;
break;


case ET_EXIT:
// Stop the timer.
// Generally clean up this state.
break;
}
return next_state;
}

更多的复杂性

当编译单元变得太大时(不管你感觉是什么,我应该说大约1000行),将每个状态处理程序放在一个单独的文件中。当每个状态处理程序的长度超过两个屏幕时,将每个事件拆分为一个单独的函数,类似于拆分状态开关的方式。您可以通过多种方式实现这一点,分别使用状态或使用公共表,或组合各种方案。有些已经被其他的覆盖了。对表进行排序,如果要求速度,可以使用二分查找。

泛型编程

我希望预处理器能够处理诸如排序表或甚至从描述中生成状态机等问题,允许您“编写关于程序的程序”。我相信这就是Boost开发c++模板的目的,但我发现语法晦涩难懂。

二维表

我过去使用过状态/事件表,但我不得不说,对于最简单的情况,我不认为它们是必要的,我更喜欢switch语句的清晰性和可读性,即使它确实扩展了一个屏幕。正如其他人所指出的,对于更复杂的情况,表格很快就会失控。我在这里介绍的习惯用法允许您在需要时添加大量事件和状态,而不必维护内存消耗表(即使它可能是程序内存)。

免责声明

特殊需求可能会使这些习语变得不那么有用,但我发现它们非常清晰且易于维护。

好吧,我觉得我的和其他人的有点不同。代码和数据的分离比我在其他答案中看到的要多一些。为了写这篇文章,我认真研读了相关理论,它实现了完整的正则语言(遗憾的是,没有正则表达式)。乌尔曼,明斯基,乔姆斯基。我不能说我完全理解了,但我尽可能直接地从古代大师那里汲取了灵感:通过他们的文字。

我使用一个函数指针指向一个谓词,该谓词决定转换到“是”状态或“否”状态。这有助于为常规语言创建有限状态接受器,您可以以更类似于汇编语言的方式进行编程。 请不要被我愚蠢的名字所吓倒。'czek' == 'check'。'grok' ==[去黑客词典里查一下].

因此,对于每次迭代,czek调用一个以当前字符作为参数的谓词函数。如果谓词返回true,则消耗该字符(指针前进),并跟随'y'转换来选择下一个状态。如果谓词返回false,则字符不被消耗,并遵循'n'过渡。所以每条指令都是双向分支!我当时一定在看《梅尔的故事》

这段代码直接来自我的后记解释器,并在comp.lang.c的指导下演变成目前的形式。由于postscript基本上没有语法(只需要平衡的括号),像这样的常规语言接受器也可以作为解析器。

/* currentstr is set to the start of string by czek
and used by setrad (called by israd) to set currentrad
which is used by israddig to determine if the character
in question is valid for the specified radix
--
a little semantic checking in the syntax!
*/
char *currentstr;
int currentrad;
void setrad(void) {
char *end;
currentrad = strtol(currentstr, &end, 10);
if (*end != '#' /* just a sanity check,
the automaton should already have determined this */
||  currentrad > 36
||  currentrad < 2)
fatal("bad radix"); /* should probably be a simple syntaxerror */
}


/*
character classes
used as tests by automatons under control of czek
*/
char *alpha = "0123456789" "ABCDE" "FGHIJ" "KLMNO" "PQRST" "UVWXYZ";
#define EQ(a,b) a==b
#define WITHIN(a,b) strchr(a,b)!=NULL
int israd  (int c) {
if (EQ('#',c)) { setrad(); return true; }
return false;
}
int israddig(int c) {
return strchrnul(alpha,toupper(c))-alpha <= currentrad;
}
int isdot  (int c) {return EQ('.',c);}
int ise    (int c) {return WITHIN("eE",c);}
int issign (int c) {return WITHIN("+-",c);}
int isdel  (int c) {return WITHIN("()<>[]{}/%",c);}
int isreg  (int c) {return c!=EOF && !isspace(c) && !isdel(c);}
#undef WITHIN
#undef EQ


/*
the automaton type
*/
typedef struct { int (*pred)(int); int y, n; } test;


/*
automaton to match a simple decimal number
*/
/* /^[+-]?[0-9]+$/ */
test fsm_dec[] = {
/* 0*/ { issign,  1,  1 },
/* 1*/ { isdigit, 2, -1 },
/* 2*/ { isdigit, 2, -1 },
};
int acc_dec(int i) { return i==2; }


/*
automaton to match a radix number
*/
/* /^[0-9]+[#][a-Z0-9]+$/ */
test fsm_rad[] = {
/* 0*/ { isdigit,  1, -1 },
/* 1*/ { isdigit,  1,  2 },
/* 2*/ { israd,    3, -1 },
/* 3*/ { israddig, 4, -1 },
/* 4*/ { israddig, 4, -1 },
};
int acc_rad(int i) { return i==4; }


/*
automaton to match a real number
*/
/* /^[+-]?(d+(.d*)?)|(d*.d+)([eE][+-]?d+)?$/ */
/* represents the merge of these (simpler) expressions
[+-]?[0-9]+\.[0-9]*([eE][+-]?[0-9]+)?
[+-]?[0-9]*\.[0-9]+([eE][+-]?[0-9]+)?
The complexity comes from ensuring at least one
digit in the integer or the fraction with optional
sign and optional optionally-signed exponent.
So passing isdot in state 3 means at least one integer digit has been found
but passing isdot in state 4 means we must find at least one fraction digit
via state 5 or the whole thing is a bust.
*/
test fsm_real[] = {
/* 0*/ { issign,  1,   1 },
/* 1*/ { isdigit, 2,   4 },
/* 2*/ { isdigit, 2,   3 },
/* 3*/ { isdot,   6,   7 },
/* 4*/ { isdot,   5,  -1 },
/* 5*/ { isdigit, 6,  -1 },
/* 6*/ { isdigit, 6,   7 },
/* 7*/ { ise,     8,  -1 },
/* 8*/ { issign,  9,   9 },
/* 9*/ { isdigit, 10, -1 },
/*10*/ { isdigit, 10, -1 },
};
int acc_real(int i) {
switch(i) {
case 2: /* integer */
case 6: /* real */
case 10: /* real with exponent */
return true;
}
return false;
}


/*
Helper function for grok.
Execute automaton against the buffer,
applying test to each character:
on success, consume character and follow 'y' transition.
on failure, do not consume but follow 'n' transition.
Call yes function to determine if the ending state
is considered an acceptable final state.
A transition to -1 represents rejection by the automaton
*/
int czek (char *s, test *fsm, int (*yes)(int)) {
int sta = 0;
currentstr = s;
while (sta!=-1 && *s) {
if (fsm[sta].pred((int)*s)) {
sta=fsm[sta].y;
s++;
} else {
sta=fsm[sta].n;
}
}
return yes(sta);
}


/*
Helper function for toke.
Interpret the contents of the buffer,
trying automatons to match number formats;
and falling through to a switch for special characters.
Any token consisting of all regular characters
that cannot be interpreted as a number is an executable name
*/
object grok (state *st, char *s, int ns,
object *src,
int (*next)(state *,object *),
void (*back)(state *,int, object *)) {


if (czek(s, fsm_dec, acc_dec)) {
long num;
num = strtol(s,NULL,10);
if ((num==LONG_MAX || num==LONG_MIN) && errno==ERANGE) {
error(st,limitcheck);
/*       } else if (num > INT_MAX || num < INT_MIN) { */
/*           error(limitcheck, OP_token); */
} else {
return consint(num);
}
}


else if (czek(s, fsm_rad, acc_rad)) {
long ra,num;
ra = (int)strtol(s,NULL,10);
if (ra > 36 || ra < 2) {
error(st,limitcheck);
}
num = strtol(strchr(s,'#')+1, NULL, (int)ra);
if ((num==LONG_MAX || num==LONG_MIN) && errno==ERANGE) {
error(st,limitcheck);
/*       } else if (num > INT_MAX || num < INT_MAX) { */
/*           error(limitcheck, OP_token); */
} else {
return consint(num);
}
}


else if (czek(s, fsm_real, acc_real)) {
double num;
num = strtod(s,NULL);
if ((num==HUGE_VAL || num==-HUGE_VAL) && errno==ERANGE) {
error(st,limitcheck);
} else {
return consreal(num);
}
}


else switch(*s) {
case '(': {
int c, defer=1;
char *sp = s;


while (defer && (c=next(st,src)) != EOF ) {
switch(c) {
case '(': defer++; break;
case ')': defer--;
if (!defer) goto endstring;
break;
case '\\': c=next(st,src);
switch(c) {
case '\n': continue;
case 'a': c = '\a'; break;
case 'b': c = '\b'; break;
case 'f': c = '\f'; break;
case 'n': c = '\n'; break;
case 'r': c = '\r'; break;
case 't': c = '\t'; break;
case 'v': c = '\v'; break;
case '\'': case '\"':
case '(': case ')':
default: break;
}
}
if (sp-s>ns) error(st,limitcheck);
else *sp++ = c;
}
endstring:  *sp=0;
return cvlit(consstring(st,s,sp-s));
}


case '<': {
int c;
char d, *x = "0123456789abcdef", *sp = s;
while (c=next(st,src), c!='>' && c!=EOF) {
if (isspace(c)) continue;
if (isxdigit(c)) c = strchr(x,tolower(c)) - x;
else error(st,syntaxerror);
d = (char)c << 4;
while (isspace(c=next(st,src))) /*loop*/;
if (isxdigit(c)) c = strchr(x,tolower(c)) - x;
else error(st,syntaxerror);
d |= (char)c;
if (sp-s>ns) error(st,limitcheck);
*sp++ = d;
}
*sp = 0;
return cvlit(consstring(st,s,sp-s));
}


case '{': {
object *a;
size_t na = 100;
size_t i;
object proc;
object fin;


fin = consname(st,"}");
(a = malloc(na * sizeof(object))) || (fatal("failure to malloc"),0);
for (i=0 ; objcmp(st,a[i]=toke(st,src,next,back),fin) != 0; i++) {
if (i == na-1)
(a = realloc(a, (na+=100) * sizeof(object))) || (fatal("failure to malloc"),0);
}
proc = consarray(st,i);
{ size_t j;
for (j=0; j<i; j++) {
a_put(st, proc, j, a[j]);
}
}
free(a);
return proc;
}


case '/': {
s[1] = (char)next(st,src);
puff(st, s+2, ns-2, src, next, back);
if (s[1] == '/') {
push(consname(st,s+2));
opexec(st, op_cuts.load);
return pop();
}
return cvlit(consname(st,s+1));
}


default: return consname(st,s);
}
return null; /* should be unreachable */
}


/*
Helper function for toke.
Read into buffer any regular characters.
If we read one too many characters, put it back
unless it's whitespace.
*/
int puff (state *st, char *buf, int nbuf,
object *src,
int (*next)(state *,object *),
void (*back)(state *,int, object *)) {
int c;
char *s = buf;
while (isreg(c=next(st,src))) {
if (s-buf >= nbuf-1) return false;
*s++ = c;
}
*s = 0;
if (!isspace(c) && c != EOF) back(st,c,src); /* eat interstice */
return true;
}


/*
Helper function for Stoken Ftoken.
Read a token from src using next and back.
Loop until having read a bona-fide non-whitespace non-comment character.
Call puff to read into buffer up to next delimiter or space.
Call grok to figure out what it is.
*/
#define NBUF MAXLINE
object toke (state *st, object *src,
int (*next)(state *, object *),
void (*back)(state *, int, object *)) {
char buf[NBUF] = "", *s=buf;
int c,sta = 1;
object o;


do {
c=next(st,src);
//if (c==EOF) return null;
if (c=='%') {
if (DUMPCOMMENTS) fputc(c, stdout);
do {
c=next(st,src);
if (DUMPCOMMENTS) fputc(c, stdout);
} while (c!='\n' && c!='\f' && c!=EOF);
}
} while (c!=EOF && isspace(c));
if (c==EOF) return null;
*s++ = c;
*s = 0;
if (!isdel(c)) sta=puff(st, s,NBUF-1,src,next,back);


if (sta) {
o=grok(st,buf,NBUF-1,src,next,back);
return o;
} else {
return null;
}
}

鉴于你暗示你可以使用c++和OO代码,我建议评估“GoF”状态模式(GoF = Gang of Four,他们写了设计模式书,使设计模式成为人们关注的焦点)。

它不是特别复杂,它被广泛使用和讨论,所以很容易在网上看到例子和解释。

以后维护您代码的其他人也很可能会认出它。

如果担心效率,那么有必要进行实际的基准测试,以确保非OO方法更有效,因为有很多因素会影响性能,而且并不总是简单的OO不好,函数代码好。类似地,如果内存使用对您来说是一个限制,那么同样值得做一些测试或计算,看看如果您使用状态模式,这对于您的特定应用程序是否真的是一个问题。

以下是克雷格建议的“Gof”状态模式的一些链接:

org提供了2个不同的状态图实现:

像往常一样,增强会把你传送到模板地狱。

第一个库用于性能更关键的状态机。第二个库为您提供了从UML Statechart到代码的直接转换路径。

这里是SO问题要求在两者之间进行比较,其中两个作者都响应。

你可以使用开源库OpenFST

OpenFst是一个用于构造、组合、优化和搜索加权有限状态传感器(FSTs)的库。加权有限状态传感器是自动机,其中每个转换都有一个输入标签,一个输出标签和一个权重。我们更熟悉的有限态受体被表示为每个跃迁的输入和输出标签相等的换能器。有限状态受体用于表示字符串集(特别是正则集或有理集);有限状态换能器用于表示字符串对之间的二进制关系(具体地说,有理换能器)。权重可以用来表示进行特定转换的成本。

我非常喜欢paxdiable的答案,并决定实现我的应用程序所缺少的所有特性,如保护变量和特定于状态机的数据。

我把我的实现上传到这个网站,与社区分享。它已经使用IAR Embedded Workbench for ARM进行了测试。

https://sourceforge.net/projects/compactfsm/

Stefan Heinzmann在他的文章中给出了一个非常好的基于模板的c++状态机“框架”。

由于本文中没有完整代码下载的链接,所以我擅自将代码粘贴到一个项目中并进行检查。下面的内容是经过测试的,包括一些次要但明显缺失的部分。

这里的主要创新是编译器生成非常高效的代码。空的进入/退出操作没有成本。非空的进入/退出操作内联。编译器还在验证状态图的完整性。缺少动作会产生链接错误。唯一没有被捕获的是缺失的Top::init

这是Miro Samek实现的一个非常好的替代方案,如果您不需要缺少的东西的话——这远远不是一个完整的UML Statechart实现,尽管它正确地实现了UML语义,而Samek的代码在设计上并没有按照正确的顺序处理退出/转换/进入操作。

如果这段代码能够满足您的需要,并且您的系统有一个不错的c++编译器,那么它的性能可能会比Miro的C/ c++实现更好。编译器为您生成一个扁平的O(1)转换状态机实现。如果对组装输出的审计确认优化工作如预期的那样,那么您就接近理论性能了。最好的部分是:它相对较小,易于理解的代码。

#ifndef HSM_HPP
#define HSM_HPP


// This code is from:
// Yet Another Hierarchical State Machine
// by Stefan Heinzmann
// Overload issue 64 december 2004
// http://accu.org/index.php/journals/252


/* This is a basic implementation of UML Statecharts.
* The key observation is that the machine can only
* be in a leaf state at any given time. The composite
* states are only traversed, never final.
* Only the leaf states are ever instantiated. The composite
* states are only mechanisms used to generate code. They are
* never instantiated.
*/


// Helpers


// A gadget from Herb Sutter's GotW #71 -- depends on SFINAE
template<class D, class B>
class IsDerivedFrom {
class Yes { char a[1]; };
class No  { char a[10]; };
static Yes Test(B*); // undefined
static No Test(...); // undefined
public:
enum { Res = sizeof(Test(static_cast<D*>(0))) == sizeof(Yes) ? 1 : 0 };
};


template<bool> class Bool {};


// Top State, Composite State and Leaf State


template <typename H>
struct TopState {
typedef H Host;
typedef void Base;
virtual void handler(Host&) const = 0;
virtual unsigned getId() const = 0;
};


template <typename H, unsigned id, typename B>
struct CompState;


template <typename H, unsigned id, typename B = CompState<H, 0, TopState<H> > >
struct CompState : B {
typedef B Base;
typedef CompState<H, id, Base> This;
template <typename X> void handle(H& h, const X& x) const { Base::handle(h, x); }
static void init(H&); // no implementation
static void entry(H&) {}
static void exit(H&) {}
};


template <typename H>
struct CompState<H, 0, TopState<H> > : TopState<H> {
typedef TopState<H> Base;
typedef CompState<H, 0, Base> This;
template <typename X> void handle(H&, const X&) const {}
static void init(H&); // no implementation
static void entry(H&) {}
static void exit(H&) {}
};


template <typename H, unsigned id, typename B = CompState<H, 0, TopState<H> > >
struct LeafState : B {
typedef H Host;
typedef B Base;
typedef LeafState<H, id, Base> This;
template <typename X> void handle(H& h, const X& x) const { Base::handle(h, x); }
virtual void handler(H& h) const { handle(h, *this); }
virtual unsigned getId() const { return id; }
static void init(H& h) { h.next(obj); } // don't specialize this
static void entry(H&) {}
static void exit(H&) {}
static const LeafState obj; // only the leaf states have instances
};


template <typename H, unsigned id, typename B>
const LeafState<H, id, B> LeafState<H, id, B>::obj;


// Transition Object


template <typename C, typename S, typename T>
// Current, Source, Target
struct Tran {
typedef typename C::Host Host;
typedef typename C::Base CurrentBase;
typedef typename S::Base SourceBase;
typedef typename T::Base TargetBase;
enum { // work out when to terminate template recursion
eTB_CB = IsDerivedFrom<TargetBase, CurrentBase>::Res,
eS_CB = IsDerivedFrom<S, CurrentBase>::Res,
eS_C = IsDerivedFrom<S, C>::Res,
eC_S = IsDerivedFrom<C, S>::Res,
exitStop = eTB_CB && eS_C,
entryStop = eS_C || eS_CB && !eC_S
};
// We use overloading to stop recursion.
// The more natural template specialization
// method would require to specialize the inner
// template without specializing the outer one,
// which is forbidden.
static void exitActions(Host&, Bool<true>) {}
static void exitActions(Host&h, Bool<false>) {
C::exit(h);
Tran<CurrentBase, S, T>::exitActions(h, Bool<exitStop>());
}
static void entryActions(Host&, Bool<true>) {}
static void entryActions(Host& h, Bool<false>) {
Tran<CurrentBase, S, T>::entryActions(h, Bool<entryStop>());
C::entry(h);
}
Tran(Host & h) : host_(h) {
exitActions(host_, Bool<false>());
}
~Tran() {
Tran<T, S, T>::entryActions(host_, Bool<false>());
T::init(host_);
}
Host& host_;
};


// Initializer for Compound States


template <typename T>
struct Init {
typedef typename T::Host Host;
Init(Host& h) : host_(h) {}
~Init() {
T::entry(host_);
T::init(host_);
}
Host& host_;
};


#endif // HSM_HPP

下面是测试代码。

#include <cstdio>
#include "hsm.hpp"
#include "hsmtest.hpp"


/* Implements the following state machine from Miro Samek's
* Practical Statecharts in C/C++
*
* |-init-----------------------------------------------------|
* |                           s0                             |
* |----------------------------------------------------------|
* |                                                          |
* |    |-init-----------|        |-------------------------| |
* |    |       s1       |---c--->|            s2           | |
* |    |----------------|<--c----|-------------------------| |
* |    |                |        |                         | |
* |<-d-| |-init-------| |        | |-init----------------| | |
* |    | |     s11    |<----f----| |          s21        | | |
* | /--| |------------| |        | |---------------------| | |
* | a  | |            | |        | |                     | | |
* | \->| |            |------g--------->|-init------|    | | |
* |    | |____________| |        | |-b->|    s211   |---g--->|
* |    |----b---^       |------f------->|           |    | | |
* |    |________________|        | |<-d-|___________|<--e----|
* |                              | |_____________________| | |
* |                              |_________________________| |
* |__________________________________________________________|
*/


class TestHSM;


typedef CompState<TestHSM,0>     Top;
typedef CompState<TestHSM,1,Top>   S0;
typedef CompState<TestHSM,2,S0>      S1;
typedef LeafState<TestHSM,3,S1>        S11;
typedef CompState<TestHSM,4,S0>      S2;
typedef CompState<TestHSM,5,S2>        S21;
typedef LeafState<TestHSM,6,S21>         S211;


enum Signal { A_SIG, B_SIG, C_SIG, D_SIG, E_SIG, F_SIG, G_SIG, H_SIG };


class TestHSM {
public:
TestHSM() { Top::init(*this); }
~TestHSM() {}
void next(const TopState<TestHSM>& state) {
state_ = &state;
}
Signal getSig() const { return sig_; }
void dispatch(Signal sig) {
sig_ = sig;
state_->handler(*this);
}
void foo(int i) {
foo_ = i;
}
int foo() const {
return foo_;
}
private:
const TopState<TestHSM>* state_;
Signal sig_;
int foo_;
};


bool testDispatch(char c) {
static TestHSM test;
if (c<'a' || 'h'<c) {
return false;
}
printf("Signal<-%c", c);
test.dispatch((Signal)(c-'a'));
printf("\n");
return true;
}


int main(int, char**) {
testDispatch('a');
testDispatch('e');
testDispatch('e');
testDispatch('a');
testDispatch('h');
testDispatch('h');
return 0;
}


#define HSMHANDLER(State) \
template<> template<typename X> inline void State::handle(TestHSM& h, const X& x) const


HSMHANDLER(S0) {
switch (h.getSig()) {
case E_SIG: { Tran<X, This, S211> t(h);
printf("s0-E;");
return; }
default:
break;
}
return Base::handle(h, x);
}


HSMHANDLER(S1) {
switch (h.getSig()) {
case A_SIG: { Tran<X, This, S1> t(h);
printf("s1-A;"); return; }
case B_SIG: { Tran<X, This, S11> t(h);
printf("s1-B;"); return; }
case C_SIG: { Tran<X, This, S2> t(h);
printf("s1-C;"); return; }
case D_SIG: { Tran<X, This, S0> t(h);
printf("s1-D;"); return; }
case F_SIG: { Tran<X, This, S211> t(h);
printf("s1-F;"); return; }
default: break;
}
return Base::handle(h, x);
}


HSMHANDLER(S11) {
switch (h.getSig()) {
case G_SIG: { Tran<X, This, S211> t(h);
printf("s11-G;"); return; }
case H_SIG: if (h.foo()) {
printf("s11-H");
h.foo(0); return;
} break;
default: break;
}
return Base::handle(h, x);
}


HSMHANDLER(S2) {
switch (h.getSig()) {
case C_SIG: { Tran<X, This, S1> t(h);
printf("s2-C"); return; }
case F_SIG: { Tran<X, This, S11> t(h);
printf("s2-F"); return; }
default: break;
}
return Base::handle(h, x);
}


HSMHANDLER(S21) {
switch (h.getSig()) {
case B_SIG: { Tran<X, This, S211> t(h);
printf("s21-B;"); return; }
case H_SIG: if (!h.foo()) {
Tran<X, This, S21> t(h);
printf("s21-H;"); h.foo(1);
return;
} break;
default: break;
}
return Base::handle(h, x);
}


HSMHANDLER(S211) {
switch (h.getSig()) {
case D_SIG: { Tran<X, This, S21> t(h);
printf("s211-D;"); return; }
case G_SIG: { Tran<X, This, S0> t(h);
printf("s211-G;"); return; }
}
return Base::handle(h, x);
}


#define HSMENTRY(State) \
template<> inline void State::entry(TestHSM&) { \
printf(#State "-ENTRY;"); \
}


HSMENTRY(S0)
HSMENTRY(S1)
HSMENTRY(S11)
HSMENTRY(S2)
HSMENTRY(S21)
HSMENTRY(S211)


#define HSMEXIT(State) \
template<> inline void State::exit(TestHSM&) { \
printf(#State "-EXIT;"); \
}


HSMEXIT(S0)
HSMEXIT(S1)
HSMEXIT(S11)
HSMEXIT(S2)
HSMEXIT(S21)
HSMEXIT(S211)


#define HSMINIT(State, InitState) \
template<> inline void State::init(TestHSM& h) { \
Init<InitState> i(h); \
printf(#State "-INIT;"); \
}


HSMINIT(Top, S0)
HSMINIT(S0, S1)
HSMINIT(S1, S11)
HSMINIT(S2, S21)
HSMINIT(S21, S211)

另一个有趣的开源工具是雅金都statecharts.org上的statecharttools。它利用Harel状态图,从而提供分层和并行状态,并生成C和c++(以及Java)代码。它不使用库,而是遵循“纯代码”方法。代码基本上应用了开关箱结构。代码生成器也可以自定义。此外,该工具还提供了许多其他特性。

这是一篇有很多答案的老文章,但我想我应该在C语言的有限状态机中添加我自己的方法。我编写了一个Python脚本来生成任意数量状态的C框架代码。该脚本被记录在github的FsmTemplateC

这个例子是基于我读过的其他方法。它不使用goto或switch语句,而是在指针矩阵(查找表)中使用转换函数。代码依赖于一个大的多行初始化器宏和C99特性(指定初始化器和复合字面量),所以如果你不喜欢这些东西,你可能不喜欢这种方法。

下面是一个十字转门的例子的Python脚本,它使用FsmTemplateC生成c代码骨架:

# dict parameter for generating FSM
fsm_param = {
# main FSM struct type string
'type': 'FsmTurnstile',
# struct type and name for passing data to state machine functions
# by pointer (these custom names are optional)
'fopts': {
'type': 'FsmTurnstileFopts',
'name': 'fopts'
},
# list of states
'states': ['locked', 'unlocked'],
# list of inputs (can be any length > 0)
'inputs': ['coin', 'push'],
# map inputs to commands (next desired state) using a transition table
# index of array corresponds to 'inputs' array
# for this example, index 0 is 'coin', index 1 is 'push'
'transitiontable': {
# current state |  'coin'  |  'push'  |
'locked':       ['unlocked',        ''],
'unlocked':     [        '',  'locked']
}
}


# folder to contain generated code
folder = 'turnstile_example'
# function prefix
prefix = 'fsm_turnstile'


# generate FSM code
code = fsm.Fsm(fsm_param).genccode(folder, prefix)

生成的输出头包含typedefs:

/* function options (EDIT) */
typedef struct FsmTurnstileFopts {
/* define your options struct here */
} FsmTurnstileFopts;


/* transition check */
typedef enum eFsmTurnstileCheck {
EFSM_TURNSTILE_TR_RETREAT,
EFSM_TURNSTILE_TR_ADVANCE,
EFSM_TURNSTILE_TR_CONTINUE,
EFSM_TURNSTILE_TR_BADINPUT
} eFsmTurnstileCheck;


/* states (enum) */
typedef enum eFsmTurnstileState {
EFSM_TURNSTILE_ST_LOCKED,
EFSM_TURNSTILE_ST_UNLOCKED,
EFSM_TURNSTILE_NUM_STATES
} eFsmTurnstileState;


/* inputs (enum) */
typedef enum eFsmTurnstileInput {
EFSM_TURNSTILE_IN_COIN,
EFSM_TURNSTILE_IN_PUSH,
EFSM_TURNSTILE_NUM_INPUTS,
EFSM_TURNSTILE_NOINPUT
} eFsmTurnstileInput;


/* finite state machine struct */
typedef struct FsmTurnstile {
eFsmTurnstileInput input;
eFsmTurnstileCheck check;
eFsmTurnstileState cur;
eFsmTurnstileState cmd;
eFsmTurnstileState **transition_table;
void (***state_transitions)(struct FsmTurnstile *, FsmTurnstileFopts *);
void (*run)(struct FsmTurnstile *, FsmTurnstileFopts *, const eFsmTurnstileInput);
} FsmTurnstile;


/* transition functions */
typedef void (*pFsmTurnstileStateTransitions)(struct FsmTurnstile *, FsmTurnstileFopts *);
  • enum eFsmTurnstileCheck用于确定转换是否被EFSM_TURNSTILE_TR_RETREAT阻塞,是否允许使用EFSM_TURNSTILE_TR_ADVANCE进行,或者函数调用之前没有使用EFSM_TURNSTILE_TR_CONTINUE进行转换。
  • enum eFsmTurnstileState是简单的状态列表。
  • enum eFsmTurnstileInput是简单的输入列表。
  • FsmTurnstile结构体是状态机的核心,包含转换检查、函数查找表、当前状态、命令状态和运行该机器的主要函数的别名。
  • FsmTurnstile中的每个函数指针(别名)只能从结构中调用,并且必须将其第一个输入作为指向自身的指针,以便保持持久状态,面向对象风格。

现在来看头文件中的函数声明:

/* fsm declarations */
void fsm_turnstile_locked_locked (FsmTurnstile *fsm, FsmTurnstileFopts *fopts);
void fsm_turnstile_locked_unlocked (FsmTurnstile *fsm, FsmTurnstileFopts *fopts);
void fsm_turnstile_unlocked_locked (FsmTurnstile *fsm, FsmTurnstileFopts *fopts);
void fsm_turnstile_unlocked_unlocked (FsmTurnstile *fsm, FsmTurnstileFopts *fopts);
void fsm_turnstile_run (FsmTurnstile *fsm, FsmTurnstileFopts *fopts, const eFsmTurnstileInput input);

函数名的格式为{prefix}_{from}_{to},其中{from}是前一个(当前)状态,{to}是下一个状态。注意,如果转换表不允许某些转换,将设置一个NULL指针而不是函数指针。最后,奇迹发生在宏上。在这里,我们构建了转换表(状态枚举矩阵)和状态转换函数查找表(函数指针矩阵):

/* creation macro */
#define FSM_TURNSTILE_CREATE() \
{ \
.input = EFSM_TURNSTILE_NOINPUT, \
.check = EFSM_TURNSTILE_TR_CONTINUE, \
.cur = EFSM_TURNSTILE_ST_LOCKED, \
.cmd = EFSM_TURNSTILE_ST_LOCKED, \
.transition_table = (eFsmTurnstileState * [EFSM_TURNSTILE_NUM_STATES]) { \
(eFsmTurnstileState [EFSM_TURNSTILE_NUM_INPUTS]) { \
EFSM_TURNSTILE_ST_UNLOCKED, \
EFSM_TURNSTILE_ST_LOCKED \
}, \
(eFsmTurnstileState [EFSM_TURNSTILE_NUM_INPUTS]) { \
EFSM_TURNSTILE_ST_UNLOCKED, \
EFSM_TURNSTILE_ST_LOCKED \
} \
}, \
.state_transitions = (pFsmTurnstileStateTransitions * [EFSM_TURNSTILE_NUM_STATES]) { \
(pFsmTurnstileStateTransitions [EFSM_TURNSTILE_NUM_STATES]) { \
fsm_turnstile_locked_locked, \
fsm_turnstile_locked_unlocked \
}, \
(pFsmTurnstileStateTransitions [EFSM_TURNSTILE_NUM_STATES]) { \
fsm_turnstile_unlocked_locked, \
fsm_turnstile_unlocked_unlocked \
} \
}, \
.run = fsm_turnstile_run \
}

创建FSM时,必须使用宏FSM_EXAMPLE_CREATE()

现在,在源代码中,上面声明的每个状态转换函数都应该被填充。FsmTurnstileFopts结构体可用于向状态机传递数据或从状态机传递数据。每个转换必须将fsm->check设置为等于EFSM_EXAMPLE_TR_RETREAT以阻止它转换,或EFSM_EXAMPLE_TR_ADVANCE以允许它转换到命令状态。 可以在(FsmTemplateC)[https://github.com/ChisholmKyle/FsmTemplateC].

.]中找到一个工作示例

下面是代码中非常简单的实际用法:

/* create fsm */
FsmTurnstile fsm = FSM_TURNSTILE_CREATE();
/* create fopts */
FsmTurnstileFopts fopts = {
.msg = ""
};
/* initialize input */
eFsmTurnstileInput input = EFSM_TURNSTILE_NOINPUT;


/* main loop */
for (;;) {
/* wait for timer signal, inputs, interrupts, whatever */
/* optionally set the input (my_input = EFSM_TURNSTILE_IN_PUSH for example) */
/* run state machine */
my_fsm.run(&my_fsm, &my_fopts, my_input);
}

在我看来,为了拥有一个简单快速的界面,所有的头部业务和所有这些功能都是值得的。

void (* StateController)(void);
void state1(void);
void state2(void);


void main()
{
StateController=&state1;
while(1)
{
(* StateController)();
}
}


void state1(void)
{
//do something in state1
StateController=&state2;
}


void state2(void)
{
//do something in state2
//Keep changing function direction based on state transition
StateController=&state1;
}
我个人将自引用结构体与指针数组结合使用。 不久前我在github上上传了一个教程,链接:

https://github.com/mmelchger/polling_state_machine_c

注意:我确实意识到这个线程相当老了,但我希望获得关于状态机设计的输入和想法,以及能够为C中可能的状态机设计提供一个示例。

下面是一个使用消息队列作为事件的Linux有限状态机示例。事件被放入队列并按顺序处理。状态的变化取决于每个事件发生的情况。

这是一个数据连接的例子,状态如下:

  • 未初始化
  • 初始化
  • 连接
  • MTU协商
  • 通过身份验证

我添加的一个小额外功能是每个消息/事件的时间戳。事件处理程序将忽略太旧的事件(它们已经过期)。这在现实世界中经常发生,你可能会意外地陷入一种状态。

这个例子运行在Linux上,使用下面的Makefile来编译它,然后摆弄它。

state_machine.c

#include <stdio.h>
#include <stdint.h>
#include <assert.h>
#include <unistd.h>   // sysconf()
#include <errno.h>    // errno
#include <string.h>   // strerror()
#include <sys/time.h> // gettimeofday()
#include <fcntl.h>    // For O_* constants
#include <sys/stat.h> // For mode constants


#include <mqueue.h>
#include <poll.h>


//------------------------------------------------
// States
//------------------------------------------------
typedef enum
{
ST_UNKNOWN = 0,
ST_UNINIT,
ST_INIT,
ST_CONNECTED,
ST_MTU_NEGOTIATED,
ST_AUTHENTICATED,
ST_ERROR,
ST_DONT_CHANGE,
ST_TERM,
} fsmState_t;


//------------------------------------------------
// Events
//------------------------------------------------
typedef enum
{
EV_UNKNOWN = 0,
EV_INIT_SUCCESS,
EV_INIT_FAIL,
EV_MASTER_CMD_MSG,
EV_CONNECT_SUCCESS,
EV_CONNECT_FAIL,
EV_MTU_SUCCESS,
EV_MTU_FAIL,
EV_AUTH_SUCCESS,
EV_AUTH_FAIL,
EV_TX_SUCCESS,
EV_TX_FAIL,
EV_DISCONNECTED,
EV_DISCON_FAILED,
EV_LAST_ENTRY,
} fsmEvName_t;


typedef struct fsmEvent_type
{
fsmEvName_t name;
struct timeval genTime; // Time the event was generated.
// This allows us to see how old the event is.
} fsmEvent_t;


// Finite State Machine Data Members
typedef struct fsmData_type
{
int  connectTries;
int  MTUtries;
int  authTries;
int  txTries;
} fsmData_t;


// Each row of the state table
typedef struct stateTable_type {
fsmState_t  st;             // Current state
fsmEvName_t evName;         // Got this event
int (*conditionfn)(void *);  // If this condition func returns TRUE
fsmState_t nextState;       // Change to this state and
void (*fn)(void *);          // Run this function
} stateTable_t;


// Finite State Machine state structure
typedef struct fsm_type
{
const stateTable_t *pStateTable; // Pointer to state table
int        numStates;            // Number of entries in the table
fsmState_t currentState;         // Current state
fsmEvent_t currentEvent;         // Current event
fsmData_t *fsmData;              // Pointer to the data attributes
mqd_t      mqdes;                // Message Queue descriptor
mqd_t      master_cmd_mqdes;     // Master command message queue
} fsm_t;


// Wildcard events and wildcard state
#define   EV_ANY    -1
#define   ST_ANY    -1
#define   TRUE     (1)
#define   FALSE    (0)


// Maximum priority for message queues (see "man mq_overview")
#define FSM_PRIO  (sysconf(_SC_MQ_PRIO_MAX) - 1)


static void addev                              (fsm_t *fsm, fsmEvName_t ev);
static void doNothing                          (void *fsm) {addev(fsm, EV_MASTER_CMD_MSG);}
static void doInit                             (void *fsm) {addev(fsm, EV_INIT_SUCCESS);}
static void doConnect                          (void *fsm) {addev(fsm, EV_CONNECT_SUCCESS);}
static void doMTU                              (void *fsm) {addev(fsm, EV_MTU_SUCCESS);}
static void reportFailConnect                  (void *fsm) {addev(fsm, EV_ANY);}
static void doAuth                             (void *fsm) {addev(fsm, EV_AUTH_SUCCESS);}
static void reportDisConnect                   (void *fsm) {addev(fsm, EV_ANY);}
static void doDisconnect                       (void *fsm) {addev(fsm, EV_ANY);}
static void doTransaction                      (void *fsm) {addev(fsm, EV_TX_FAIL);}
static void fsmError                           (void *fsm) {addev(fsm, EV_ANY);}


static int currentlyLessThanMaxConnectTries    (void *fsm) {
fsm_t *l = (fsm_t *)fsm;
return (l->fsmData->connectTries < 5 ? TRUE : FALSE);
}
static int        isMoreThanMaxConnectTries    (void *fsm) {return TRUE;}
static int currentlyLessThanMaxMTUtries        (void *fsm) {return TRUE;}
static int        isMoreThanMaxMTUtries        (void *fsm) {return TRUE;}
static int currentyLessThanMaxAuthTries        (void *fsm) {return TRUE;}
static int       isMoreThanMaxAuthTries        (void *fsm) {return TRUE;}
static int currentlyLessThanMaxTXtries         (void *fsm) {return FALSE;}
static int        isMoreThanMaxTXtries         (void *fsm) {return TRUE;}
static int didNotSelfDisconnect                (void *fsm) {return TRUE;}


static int  waitForEvent                       (fsm_t *fsm);
static void runEvent                           (fsm_t *fsm);
static void runStateMachine(fsm_t *fsm);
static int newEventIsValid(fsmEvent_t *event);
static void getTime(struct timeval *time);
void printState(fsmState_t st);
void printEvent(fsmEvName_t ev);


// Global State Table
const stateTable_t GST[] = {
// Current state         Got this event          If this condition func returns TRUE     Change to this state and    Run this function
{ ST_UNINIT,             EV_INIT_SUCCESS,        NULL,                                   ST_INIT,                    &doNothing              },
{ ST_UNINIT,             EV_INIT_FAIL,           NULL,                                   ST_UNINIT,                  &doInit                 },
{ ST_INIT,               EV_MASTER_CMD_MSG,      NULL,                                   ST_INIT,                    &doConnect              },
{ ST_INIT,               EV_CONNECT_SUCCESS,     NULL,                                   ST_CONNECTED,               &doMTU                  },
{ ST_INIT,               EV_CONNECT_FAIL,        &currentlyLessThanMaxConnectTries,      ST_INIT,                    &doConnect              },
{ ST_INIT,               EV_CONNECT_FAIL,        &isMoreThanMaxConnectTries,             ST_INIT,                    &reportFailConnect      },
{ ST_CONNECTED,          EV_MTU_SUCCESS,         NULL,                                   ST_MTU_NEGOTIATED,          &doAuth                 },
{ ST_CONNECTED,          EV_MTU_FAIL,            &currentlyLessThanMaxMTUtries,          ST_CONNECTED,               &doMTU                  },
{ ST_CONNECTED,          EV_MTU_FAIL,            &isMoreThanMaxMTUtries,                 ST_CONNECTED,               &doDisconnect           },
{ ST_CONNECTED,          EV_DISCONNECTED,        &didNotSelfDisconnect,                  ST_INIT,                    &reportDisConnect       },
{ ST_MTU_NEGOTIATED,     EV_AUTH_SUCCESS,        NULL,                                   ST_AUTHENTICATED,           &doTransaction          },
{ ST_MTU_NEGOTIATED,     EV_AUTH_FAIL,           &currentyLessThanMaxAuthTries,          ST_MTU_NEGOTIATED,          &doAuth                 },
{ ST_MTU_NEGOTIATED,     EV_AUTH_FAIL,           &isMoreThanMaxAuthTries,                ST_MTU_NEGOTIATED,          &doDisconnect           },
{ ST_MTU_NEGOTIATED,     EV_DISCONNECTED,        &didNotSelfDisconnect,                  ST_INIT,                    &reportDisConnect       },
{ ST_AUTHENTICATED,      EV_TX_SUCCESS,          NULL,                                   ST_AUTHENTICATED,           &doDisconnect           },
{ ST_AUTHENTICATED,      EV_TX_FAIL,             &currentlyLessThanMaxTXtries,           ST_AUTHENTICATED,           &doTransaction          },
{ ST_AUTHENTICATED,      EV_TX_FAIL,             &isMoreThanMaxTXtries,                  ST_AUTHENTICATED,           &doDisconnect           },
{ ST_AUTHENTICATED,      EV_DISCONNECTED,        &didNotSelfDisconnect,                  ST_INIT,                    &reportDisConnect       },
{ ST_ANY,                EV_DISCON_FAILED,       NULL,                                   ST_DONT_CHANGE,             &doDisconnect           },
{ ST_ANY,                EV_ANY,                 NULL,                                   ST_UNINIT,                  &fsmError               }    // Wildcard state for errors
};


#define GST_COUNT (sizeof(GST)/sizeof(stateTable_t))


int main()
{
int ret = 0;
fsmData_t dataAttr;
dataAttr.connectTries = 0;
dataAttr.MTUtries     = 0;
dataAttr.authTries    = 0;
dataAttr.txTries      = 0;


fsm_t lfsm;
memset(&lfsm, 0, sizeof(fsm_t));
lfsm.pStateTable       = GST;
lfsm.numStates         = GST_COUNT;
lfsm.currentState      = ST_UNINIT;
lfsm.currentEvent.name = EV_ANY;
lfsm.fsmData           = &dataAttr;


struct mq_attr attr;
attr.mq_maxmsg = 30;
attr.mq_msgsize = sizeof(fsmEvent_t);


// Dev info
//printf("Size of fsmEvent_t [%ld]\n", sizeof(fsmEvent_t));


ret = mq_unlink("/abcmq");
if (ret == -1) {
fprintf(stderr, "Error on mq_unlink(), errno[%d] strerror[%s]\n",
errno, strerror(errno));
}


lfsm.mqdes = mq_open("/abcmq", O_CREAT | O_RDWR, S_IWUSR | S_IRUSR, &attr);
if (lfsm.mqdes == (mqd_t)-1) {
fprintf(stderr, "Error on mq_open(), errno[%d] strerror[%s]\n",
errno, strerror(errno));
return -1;
}


doInit(&lfsm);  // This will generate the first event
runStateMachine(&lfsm);


return 0;
}




static void runStateMachine(fsm_t *fsm)
{
int ret = 0;


if (fsm == NULL) {
fprintf(stderr, "[%s] NULL argument\n", __func__);
return;
}


// Cycle through the state machine
while (fsm->currentState != ST_TERM) {
printf("current state [");
printState(fsm->currentState);
printf("]\n");


ret = waitForEvent(fsm);
if (ret == 0) {
printf("got event [");
printEvent(fsm->currentEvent.name);
printf("]\n");


runEvent(fsm);
}
sleep(2);
}
}




static int waitForEvent(fsm_t *fsm)
{
//const int numFds = 2;
const int numFds = 1;
struct pollfd fds[numFds];
int timeout_msecs = -1; // -1 is forever
int ret = 0;
int i = 0;
ssize_t num = 0;
fsmEvent_t newEv;


if (fsm == NULL) {
fprintf(stderr, "[%s] NULL argument\n", __func__);
return -1;
}


fsm->currentEvent.name = EV_ANY;


fds[0].fd     = fsm->mqdes;
fds[0].events = POLLIN;
//fds[1].fd     = fsm->master_cmd_mqdes;
//fds[1].events = POLLIN;
ret = poll(fds, numFds, timeout_msecs);


if (ret > 0) {
// An event on one of the fds has occurred
for (i = 0; i < numFds; i++) {
if (fds[i].revents & POLLIN) {
// Data may be read on device number i
num = mq_receive(fds[i].fd, (void *)(&newEv),
sizeof(fsmEvent_t), NULL);
if (num == -1) {
fprintf(stderr, "Error on mq_receive(), errno[%d] "
"strerror[%s]\n", errno, strerror(errno));
return -1;
}


if (newEventIsValid(&newEv)) {
fsm->currentEvent = newEv;
} else {
return -1;
}
}
}
} else {
fprintf(stderr, "Error on poll(), ret[%d] errno[%d] strerror[%s]\n",
ret, errno, strerror(errno));
return -1;
}


return 0;
}




static int newEventIsValid(fsmEvent_t *event)
{
if (event == NULL) {
fprintf(stderr, "[%s] NULL argument\n", __func__);
return FALSE;
}


printf("[%s]\n", __func__);


struct timeval now;
getTime(&now);


if ( (event->name < EV_LAST_ENTRY) &&
((now.tv_sec - event->genTime.tv_sec) < (60*5))
)
{
return TRUE;
} else {
return FALSE;
}
}




//------------------------------------------------
// Performs event handling on the FSM (finite state machine).
// Make sure there is a wildcard state at the end of
// your table, otherwise; the event will be ignored.
//------------------------------------------------
static void runEvent(fsm_t *fsm)
{
int i;
int condRet = 0;


if (fsm == NULL) {
fprintf(stderr, "[%s] NULL argument\n", __func__);
return;
}


printf("[%s]\n", __func__);


// Find a relevant entry for this state and event
for (i = 0; i < fsm->numStates; i++) {
// Look in the table for our current state or ST_ANY
if (  (fsm->pStateTable[i].st == fsm->currentState) ||
(fsm->pStateTable[i].st == ST_ANY)
)
{
// Is this the event we are looking for?
if ( (fsm->pStateTable[i].evName == fsm->currentEvent.name) ||
(fsm->pStateTable[i].evName == EV_ANY)
)
{
if (fsm->pStateTable[i].conditionfn != NULL) {
condRet = fsm->pStateTable[i].conditionfn(fsm->fsmData);
}


// See if there is a condition associated
// or we are not looking for any condition
//
if ( (condRet != 0) || (fsm->pStateTable[i].conditionfn == NULL))
{
// Set the next state (if applicable)
if (fsm->pStateTable[i].nextState != ST_DONT_CHANGE) {
fsm->currentState = fsm->pStateTable[i].nextState;
printf("new state [");
printState(fsm->currentState);
printf("]\n");
}


// Call the state callback function
fsm->pStateTable[i].fn(fsm);
break;
}
}
}
}
}




//------------------------------------------------
//               EVENT HANDLERS
//------------------------------------------------
static void getTime(struct timeval *time)
{
if (time == NULL) {
fprintf(stderr, "[%s] NULL argument\n", __func__);
return;
}


printf("[%s]\n", __func__);


int ret = gettimeofday(time, NULL);
if (ret != 0) {
fprintf(stderr, "gettimeofday() failed: errno [%d], strerror [%s]\n",
errno, strerror(errno));
memset(time, 0, sizeof(struct timeval));
}
}




static void addev (fsm_t *fsm, fsmEvName_t ev)
{
int ret = 0;


if (fsm == NULL) {
fprintf(stderr, "[%s] NULL argument\n", __func__);
return;
}


printf("[%s] ev[%d]\n", __func__, ev);


if (ev == EV_ANY) {
// Don't generate a new event, just return...
return;
}


fsmEvent_t newev;
getTime(&(newev.genTime));
newev.name = ev;


ret = mq_send(fsm->mqdes, (void *)(&newev), sizeof(fsmEvent_t), FSM_PRIO);
if (ret == -1) {
fprintf(stderr, "[%s] mq_send() failed: errno [%d], strerror [%s]\n",
__func__, errno, strerror(errno));
}
}
//------------------------------------------------
//           end EVENT HANDLERS
//------------------------------------------------


void printState(fsmState_t st)
{
switch(st) {
case    ST_UNKNOWN:
printf("ST_UNKNOWN");
break;
case    ST_UNINIT:
printf("ST_UNINIT");
break;
case    ST_INIT:
printf("ST_INIT");
break;
case    ST_CONNECTED:
printf("ST_CONNECTED");
break;
case    ST_MTU_NEGOTIATED:
printf("ST_MTU_NEGOTIATED");
break;
case    ST_AUTHENTICATED:
printf("ST_AUTHENTICATED");
break;
case    ST_ERROR:
printf("ST_ERROR");
break;
case    ST_TERM:
printf("ST_TERM");
break;
default:
printf("unknown state");
break;
}
}


void printEvent(fsmEvName_t ev)
{
switch (ev) {
case    EV_UNKNOWN:
printf("EV_UNKNOWN");
break;
case    EV_INIT_SUCCESS:
printf("EV_INIT_SUCCESS");
break;
case    EV_INIT_FAIL:
printf("EV_INIT_FAIL");
break;
case    EV_MASTER_CMD_MSG:
printf("EV_MASTER_CMD_MSG");
break;
case    EV_CONNECT_SUCCESS:
printf("EV_CONNECT_SUCCESS");
break;
case    EV_CONNECT_FAIL:
printf("EV_CONNECT_FAIL");
break;
case    EV_MTU_SUCCESS:
printf("EV_MTU_SUCCESS");
break;
case    EV_MTU_FAIL:
printf("EV_MTU_FAIL");
break;
case    EV_AUTH_SUCCESS:
printf("EV_AUTH_SUCCESS");
break;
case    EV_AUTH_FAIL:
printf("EV_AUTH_FAIL");
break;
case    EV_TX_SUCCESS:
printf("EV_TX_SUCCESS");
break;
case    EV_TX_FAIL:
printf("EV_TX_FAIL");
break;
case    EV_DISCONNECTED:
printf("EV_DISCONNECTED");
break;
case    EV_LAST_ENTRY:
printf("EV_LAST_ENTRY");
break;
default:
printf("unknown event");
break;
}
}

Makefile

CXX = gcc
COMPFLAGS = -c -Wall -g


state_machine: state_machine.o
$(CXX) -lrt state_machine.o -o state_machine


state_machine.o: state_machine.c
$(CXX) $(COMPFLAGS) state_machine.c


clean:
rm state_machine state_machine.o

你可以考虑UML-state-machine-in-c, a "lightweight"我写了这个框架来支持有限状态机分层状态机。与状态表或简单的开关情况相比,框架方法更具可伸缩性。它可以用于简单的有限状态机到复杂的分层状态机。

状态机由state_machine_t结构表示。它只包含两个成员"Event"和一个指向“state_t”的指针。

struct state_machine_t
{
uint32_t Event;          //!< Pending Event for state machine
const state_t* State;    //!< State of state machine.
};

state_machine_t必须是状态机结构的第一个成员。如。

struct user_state_machine
{
state_machine_t Machine;    // Base state machine. Must be the first member of user derived state machine.


// User specific state machine members
uint32_t param1;
uint32_t param2;
...
};

state_t包含一个用于状态的处理程序,以及用于进入和退出操作的可选处理程序。

//! finite state structure
struct finite_state{
state_handler Handler;      //!< State handler to handle event of the state
state_handler Entry;        //!< Entry action for state
state_handler Exit;          //!< Exit action for state.
};

如果框架配置为分层状态机,则state_t包含指向父状态和子状态的指针。

框架提供了一个API dispatch_event来分派事件到状态机,switch_state来触发状态转换。

有关如何实现分层状态机的更多详细信息,请参阅GitHub存储库。

代码示例,

https://github.com/kiishor/UML-State-Machine-in-C/blob/master/demo/simple_state_machine/readme.md https://github.com/kiishor/UML-State-Machine-in-C/blob/master/demo/simple_state_machine_enhanced/readme.md < / p >

下面是一个使用宏的状态机方法,这样每个函数都可以有自己的状态集:https://www.codeproject.com/Articles/37037/Macros-to-simulate-multi-tasking-blocking-code-at

它的标题是“模拟多任务”,但这不是唯一的用途。

这个方法使用回调来从每个函数停止的地方开始拾取。每个函数都包含一个单独的状态列表。一个中央“空闲循环”用于运行状态机。“空闲循环”不知道状态机是如何工作的,只有个别函数“知道该做什么”。为了编写函数的代码,只需创建一个状态列表,并使用宏来“挂起”和“恢复”。我在Cisco为Nexus 7000交换机编写收发器库时使用了这些宏。