状态机教程 [已结束]

state machines tutorials [closed]

提问人:ant2009 提问时间:9/3/2009 最后编辑:paradigmaticant2009 更新时间:9/6/2019 访问量:159419

问:


我们不允许向读者、工具、软件库等寻求推荐的问题。您可以编辑问题,以便用事实和引文来回答。

7年前关闭。

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

我开始研究状态机,只需要一些通用的东西来让我入门。

C C99 状态机

评论

3赞 paxdiablo 10/30/2009
另请参阅此处:stackoverflow.com/questions/1647631/c-state-machine-design/...

答:

6赞 ChaosPandion 9/3/2009 #1

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

int state = 0;
while (state < 3)
{
    switch (state)
    {
        case 0:
            // Do State 0 Stuff
            if (should_go_to_next_state)
            {
                state++;
            }
            break;
        case 1:
            // Do State 1 Stuff    
            if (should_go_back) 
            {
                state--;
            }    
            else if (should_go_to_next_state) 
            {
                state++;
            }
            break;
        case 2:
            // Do State 2 Stuff    
            if (should_go_back_two) 
            {
                state -= 2;
            }    
            else if (should_go_to_next_state) 
            {
                state++;
            }
            break;
        default:
            break;
    }
}

评论

9赞 Chris Lutz 9/3/2009
我认为显式设置状态是更好的做法,但这只是我。
2赞 ChrisW 9/3/2009
你遗漏了很多,例如:子州;事件和事件/状态组合;状态转换图;守卫(“如果”,则不改变状态);状态进入和状态退出方法(“在退出此状态时,执行以下方法”)。
2赞 X-Istence 9/3/2009
这是对状态机的过度简化,而不是一个很好的例子。
2赞 ChaosPandion 9/3/2009
不要把非常简单的事情过于复杂化。
1赞 mrduclaw 9/3/2009
当然,作为基本状态机的骨架,这可能就足够了。但我认为这并不完全是“你需要知道的一切”。此外,您可能希望将状态设置为无符号状态。
3赞 ChrisW 9/3/2009 #2

Real-Time Object-Oriented Modeling 非常棒(1994 年出版,现在售价低至 81 美分,外加 3.99 美元的运费)。

评论

3赞 marcc 9/3/2009
1 个新 60.20 美元起,24 个 0.81 美元起。这很幽默。
1赞 Carl 1/31/2014
有趣的是,该链接上的引荐来源是 StackOverflow。
7赞 X-Istence 9/3/2009 #3

状态机本质上不需要教程来解释甚至使用。我的建议是,你看看数据以及如何解析它。

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

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

它以已知良好的状态进入状态机,此时我们开始解析,根据我们遇到的字符,我们要么进入下一个状态,要么回到以前的状态。这基本上允许代码自我适应数据的存储方式,甚至无论某些数据是否存在。

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

状态机编写起来很简单,一般来说,我遵循它应该流动的规则。通过系统的输入应该在一定程度上轻松地从一个状态流向另一个状态。

评论

2赞 X-Istence 9/3/2009
@Chris:近太空被定义为60,000英尺以上的任何东西,我们的气球达到了92,999英尺。在某个时候,乳胶气球开始变得如此之大,因为减压(气体不断膨胀气球)以至于气球爆裂,此时近太空船自由落体返回地球(我们连接降落伞偏离航线),请参阅链接的 Wiki 有关我们的近太空工作的更多信息和谷歌周围, 这绝对是一个很棒的爱好!
4赞 Chris Lutz 9/3/2009
这听起来像是一个非常低效、贵得离谱、也许有点浪费,而且完全很棒的爱好。
1赞 dmckee --- ex-moderator kitten 9/3/2009
许多强大而重要的实验都是从气球平台上进行的,您必须将成本与发射等效轨道平台的成本进行比较。当你到达大约100,000英尺时,你的热管理问题对航天器来说是必不可少的:与辐射相比,传导和对流加热/冷却正在消失。
1赞 X-Istence 9/4/2009
@Chris:我们的预算是2000美元,到目前为止,我们已经成功发射了两个气球。最昂贵的部分是填充乳胶气球的氦气,这是第二昂贵的部分。
1赞 Dan Bechard 1/19/2017
@ChrisLutz 我认为恰恰相反。与替代方案相比:火箭;它效率更高,成本更低,浪费也更少,但稍微不那么令人敬畏。
152赞 qrdl 9/3/2009 #4

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

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

int entry_state(void);
int foo_state(void);
int bar_state(void);
int exit_state(void);

/* array and enum below must be in sync! */
int (* state[])(void) = { entry_state, foo_state, bar_state, exit_state};
enum state_codes { entry, foo, bar, end};

enum ret_codes { ok, fail, repeat};
struct transition {
    enum state_codes src_state;
    enum ret_codes   ret_code;
    enum state_codes dst_state;
};
/* transitions from end state aren't needed */
struct transition state_transitions[] = {
    {entry, ok,     foo},
    {entry, fail,   end},
    {foo,   ok,     bar},
    {foo,   fail,   end},
    {foo,   repeat, foo},
    {bar,   ok,     end},
    {bar,   fail,   end},
    {bar,   repeat, foo}};

#define EXIT_STATE end
#define ENTRY_STATE entry

int main(int argc, char *argv[]) {
    enum state_codes cur_state = ENTRY_STATE;
    enum ret_codes rc;
    int (* state_fun)(void);

    for (;;) {
        state_fun = state[cur_state];
        rc = state_fun();
        if (EXIT_STATE == cur_state)
            break;
        cur_state = lookup_transitions(cur_state, rc);
    }

    return EXIT_SUCCESS;
}

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

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

评论

16赞 Alex 10/11/2013
很好,谢谢你。这里唯一可能的缺陷是,lookup_transitions可能是线性的 (O(n)) 与此转换表数据结构。它有可能用多维数组使它变得更好 - 保证 O(1)。例如。该表可以表示为一个多维数组,其中 Key 是状态,value 是一个数组,其中 key 是返回代码,value 是下一个状态:int state_transitions[][3] = { [entry] = { foo, end, foo }, ... } /* ok, fail, repeat */
2赞 Django 4/3/2016
为什么你的状态函数不为查找函数返回枚举而不是整数?您正在返回 ret 代码,不是吗?
0赞 Multisync 8/17/2016
将 和 作为函数指针不是更容易吗?我不明白拥有枚举类型的好处,当您使用它们只是在数组中查找一些函数指针时。src_statedst_state
3赞 qrdl 8/17/2016
@Multisync 一般来说,状态 != 函数,通常有几个不同的状态实际上使用相同的函数。例如,您可以有几个准备消息的函数,一个函数用于发送消息,一个函数用于接收响应,但这两个 I/O 函数将在不同的状态下使用。
0赞 Fuevo 10/14/2019
任何状态都可以看作是一个“sub_main_function”,由于这些“sub_main_functions”中的动作,它可以再次更改为不同的状态,我们来使用一个开关,每个“案例状态:”都有多种功能,有人和我一起吗?
12赞 Michael Burr 9/3/2009 #5

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

但是,在 C 语言中实现状态机非常容易,要么使用 switch 语句将事件调度到状态(对于简单的 FSM,它们几乎是直接编写代码),要么使用表将事件映射到状态转换。

这里有几篇关于C语言状态机基本框架的简单但不错的文章:

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

switch基于语句的状态机通常使用一组宏来“隐藏”语句的机制(或使用一组 // 语句而不是 语句),并制作相当于“FSM 语言”的东西来描述 C 源代码中的状态机。我个人更喜欢基于表的方法,但这些方法肯定有优点,被广泛使用,并且特别对更简单的 FSM 有效。switchifthenelseswitch

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

下面讨论了一组类似的宏:

如果您也对 C++ 状态机实现感兴趣,可以找到更多内容。如果你有兴趣,我会发布指针。

评论

1赞 ant2009 9/3/2009
谢谢,它们是好文章。我在这里也找到了一个。en.wikipedia.org/wiki/Event_driven_finite_state_machine
4赞 Roman Khimov 9/3/2009 #6

用 C 语言手工制作状态机有很多经验教训,但我也建议使用 Ragel 状态机编译器:

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

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

35赞 Christoph 9/5/2009 #7

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

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

#include <stdio.h>

struct state;
typedef void state_fn(struct state *);

struct state
{
    state_fn * next;
    int i; // data
};

state_fn foo, bar;

void foo(struct state * state)
{
    printf("%s %i\n", __func__, ++state->i);
    state->next = bar;
}

void bar(struct state * state)
{
    printf("%s %i\n", __func__, ++state->i);
    state->next = state->i < 10 ? foo : 0;
}

int main(void)
{
    struct state state = { foo, 0 };
    while(state.next) state.next(&state);
}

评论

0赞 dreamlax 11/20/2009
您不返回值 . . .应该吗?main
1赞 Christoph 11/21/2009
@dreamlax:C99 5.1.2.2.3:到达隐式返回的末尾main()0
1赞 AntonioCS 2/14/2015
真的很喜欢这个答案。简单而直接。干得好。
0赞 Multisync 8/17/2016
对不起,我真的不明白。在最后一行的条件下会发生什么?你在打电话吗?如果没有给出任何默认参数,则假定哪些默认参数?whilefoo()
2赞 Dan Bechard 1/19/2017
@Multisync 上一行中的结构初始值设定项将(函数指针)设置为 的地址。所以与(循环的第一次迭代,它稍后指向其他地方)相同。为了进行比较,如果这是 C++,则将是类 () 的成员,并且不采用任何参数。它的身体会用代替.在 C 中,您必须显式传递指针的等效项,因为没有有状态类作用域。state.nextfoostate.next(&state)foo(&state)foo()StateState::foo()this->next = barstate->next = barthis
-6赞 eddyq 4/26/2016 #8

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

评论

0赞 the_endian 3/22/2020
没有回答问题。取而代之的是一篇关于为什么状态机不好的社论。
0赞 Theo d'Or 3/20/2021
点赞,因为它接近唯一的正确答案,尽管确切的措辞和提供的链接是不幸的。包含状态机的自动机理论是一种计算的数学模型,没有直接的实际应用。我们在本身就是状态机的计算机上使用据说是图灵完备的编程语言,我们不会用它们来模拟图灵机,对吗?那么,我们为什么要模拟一个受约束的子概念呢?本页其他答案所描述的算法是一种迟钝,是编程中群体心态的标志。