状态机教程[关闭]


74

我只是想知道是否有人知道Internet上关于开发状态机的一些很好的教程。还是电子书?

我开始在状态机上工作,只需要一些一般性的知识即可入门。


Answers:


138

如果使用函数指针,状态机在C语言中非常简单。

基本上,您需要2个数组-一个用于状态函数指针,另一个用于状态转换规则。每个状态函数都返回代码,您可以按状态查找状态转换表,并返回代码以查找下一个状态,然后执行该状态。

我不放lookup_transitions()函数,因为它是微不足道的。

这就是我多年来使用状态机的方式。


15
很好,谢谢你。唯一可能的缺陷是,使用此过渡表数据结构,lookup_transitions可能是线性的(O(n))。使用多维数组可以使其更好-保证O(1)。例如。该表可以表示为多维数组,其中键是状态,值是一个数组,键是返回码,值是下一个状态: int state_transitions[][3] = { [entry] = { foo, end, foo }, ... } /* ok, fail, repeat */
Alex

2
为什么您的状态函数不为查询函数返回枚举而不是整数?您正在返回ret码,不是吗?
Django

拥有src_statedst_state作为函数指针会不会容易得多?我不了解使用枚举类型的它们的好处,当您使用它们时,只是在数组中查找一些函数指针。
Multisync

2
@Multisync通常来说,状态!=函数,通常有多个不同的状态实际上使用相同的功能。例如,您可以有几个准备消息的功能,一个发送消息的功能和一个接收响应的功能,但是这两个I / O功能将在不同的状态下使用。
qrdl

任何状态都可以视为一个“ sub_main_function”,由于这些“ sub_main_functions”中的动作,它可以再次更改为不同的状态,让我们使用一个开关,每个“ case state:”具有多种功能,有人陪我吗?
Fuevo

29

我喜欢使用函数指针而不是硕大的switch语句,但是与qrdl的答案相反,我通常不使用显式的返回码或转换表。

另外,在大多数情况下,您需要一种机制来传递其他数据。这是一个示例状态机:


main没有返回值。。。应该是?
dreamlax

1
@dreamlax:C99 5.1.2.2.3:main()隐式返回的结尾0
Christoph

真的很喜欢这个答案。简单直接。做得好。
AntonioCS

抱歉,我真的不明白。在发生什么while的最后一行的条件?你在打电话foo()吗 如果未给出默认参数,则假定使用哪些默认参数?
Multisync

1
@Multisync前一行中的struct初始值设定项state.next(函数指针)设置为的地址foo。因此state.next(&state)foo(&state)(循环的第一次迭代,稍后将其指向其他地方)相同。为了进行比较,如果是C ++,foo()它将是State类(State::foo())的成员,并且不接受任何参数。它的身体将this->next = bar代替state->next = bar。在C语言中this,由于没有状态类作用域,因此必须显式传递指针的等效项。

9

不幸的是,状态机上的大多数文章都是用C ++或其他直接支持多态的语言编写的,因为将FSM实现中的状态建模为派生自抽象状态类的类很不错。

然而,使用switch语句将事件分派给状态(对于简单的FSM,它们几乎是直接编写代码)或使用表将事件映射到状态转换,在C中实现状态机非常容易。

这里有一些关于C状态机基本框架的简单但不错的文章:

编辑:网站“维护中”,网络存档链接:

switch基于语句的状态机经常使用的一组宏“隐藏”的力学的switch语句(或使用一组if/ then/else语句来代替switch),并相当于一个“FSM语言”描述在C状态机资源。我个人更喜欢基于表的方法,但是这些方法肯定有优点,已被广泛使用,并且尤其对于简单的FSM而言可能是有效的。

Steve Rabin在“ Game Programming Gems”第3.0章(设计通用的健壮AI引擎)中概述了这样一种框架。

这里讨论了一组相似的宏:

如果您还对C ++状态机实现感兴趣,那么可以找到更多的信息。如果您有兴趣,我会发布指针。


1
谢谢,他们是好文章。我也在这里找到了一个。en.wikipedia.org/wiki/Event_driven_finite_state_machine
ant2009

8

状态机并不是本质上需要解释甚至使用教程的东西。我建议您查看一下数据以及如何对其进行解析。

例如,我必须为近太空气球飞行计算机解析数据协议,它以特定格式(二进制)将数据存储在SD卡上,需要将其解析为逗号分隔文件。为此,使用状态机是最有意义的,因为根据下一信息的内容,我们需要更改解析的内容。

该代码使用C ++编写,可以作为ParseFCU使用。如您所见,它首先检测我们正在解析的版本,然后从那里进入两个不同的状态机。

它以已知良好的状态进入状态机,这时我们开始解析,并根据遇到的字符前进到下一个状态,或返回到上一个状态。基本上,这使代码可以自动适应数据的存储方式,甚至可以完全适应某些数据。

在我的示例中,GPS字符串不是飞行计算机记录日志的必要条件,因此,如果找到了该单个日志写入的结尾字节,则可以跳过GPS字符串的处理。

状态机很容易编写,一般来说,我遵循应遵循的规则。通过系统的输入应该在状态之间轻松流动。


2
@克里斯:“近太空”是指60,000英尺以上的任何物体,我们的气球达到了92,999英尺。由于减压(气体不断使气球膨胀),乳胶气球开始变得如此之大,以至于气球弹出。指出近太空飞船可以自由落体(我们在降落伞上附加了降落伞),请参阅链接的Wiki,以获取有关我们的近太空工作和Google周围环境的更多信息,这绝对是一个很棒的爱好!
X-Istence's

4
这听起来像是效率低下,荒谬可笑的,也许有点浪费,而且真是太棒了。
克里斯·卢茨

1
气球平台已进行了许多功能强大且重要的实验,您必须将成本与发射同等轨道平台的成本进行比较。到大约100,000英尺时,热管理问题已成为航天器的基本问题:与辐射相比,传导和对流加热/冷却已消失。
dmckee ---前主持人小猫,

1
@克里斯:我们有2000美元的预算可以使用,到目前为止,我们已经成功发射了两个气球。最昂贵的部分是填充乳胶气球的氦气,后者是第二贵的部分。
X-Istence

1
@ChrisLutz我会争论完全相反。与替代方案相比:火箭;到目前为止,它效率更高,价格更便宜,浪费更少,但效果却稍差一些。
丹·贝查德

4

这就是您需要知道的全部。


7
我会认为更好的做法是明确设置状态,但这就是我。
克里斯·卢茨

2
您遗漏了很多东西,例如:substates; 事件以及事件/状态组合;状态转换图;警卫队(“如果不改变状态”);状态进入和状态退出方法(“退出此状态时,请执行以下方法”)。
ChrisW

2
这过于简化了状态机,而不是一个很好的例子。
X-Istence's

2
不要过度复杂一些简单的事情。
2009年

1
当然,作为基本状态机看起来像这样的骨架可能就足够了。但是我认为这并不是完全“您需要知道的所有事情”。另外,您可能想使状态为未签名。
mrduclaw


3

学习用C语言手工制作状态机有很多课程,但让我也建议Ragel状态机编译器:

http://www.complang.org/ragel/

它具有定义状态机的非常简单的方法,然后您可以生成图,生成不同样式的代码(表驱动,goto驱动),分析代码(如果需要)等等。它功能强大,可以在生产中使用各种协议的代码。


-6

对于复杂的问题,状态机可能非常复杂。它们还会遭受意外的错误。如果有人遇到错误或将来需要更改逻辑,它们可能会变成噩梦。如果没有状态图,它们也很难遵循和调试。结构化编程要好得多(例如,您可能不会在主线级别使用状态机)。您甚至可以在中断上下文(通常使用状态机)中使用结构化编程。请参阅位于codeproject.com上的本文“在中断级别模拟多任务/阻止代码的宏”


不回答问题。取而代之的是,发表关于为什么状态机不好的社论。
the_endian '20
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.