C 状态机设计

C state-machine design

我手工创建混合的 C 和 c + + 中的小型项目。我正在构建一个 small-ish 状态的机器我工作线程之一的核心。

我想知道是否在专家因此将共享状态机设计技术。

注意︰我主要后尝试和测试的实现方法。

更新︰根据收集上等的所有好输入,我已经决定这种体系结构︰

alt text

2009-10-30 02:08:33
问题评论:

这里的回答都很好。此外看过有几个很好的答案,这个重复问题︰ stackoverflow.com/questions/1371460/state-machines-tutorials

这也是有趣︰ stackoverflow.com/questions/133214/...

相关︰ stackoverflow.com/questions/2101961/python-state-machine-design

请参阅此问题.

我可以只说我真的真的真的恨你,宏的使用状态机。您认为真正有效使用宏的直到击中墙壁,然后找不到任何东西,因为它不是代码,很杂乱。

回答:

前 (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();
    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作为通配符的 alluded,使事件可以调用一个函数,无论的当前状态。EV_ANY也适用同样,允许在调用函数的特定状态的任何事件。

它还可以确保,如果达到转换数组的结尾,您收到错误消息指出您 FSM 以前没有建正确 (通过使用ST_ANY/EV_ANY组合。

我使用过类似的代码,这在很多通信项目,如早期的通信栈和协议的嵌入式系统实现。其最大优点是其简单性和改变转换数组也相对容易。

我毫无疑问是较高级别的抽象,这可能更适合如今将,但我怀疑他们将所有可以归结为这同一种结构。


而且,正如ldog所指出在注释中,您可以通过将一个结构的指针传递给所有函数完全避免全局 (和使用的在事件循环)。这将允许多个状态机运行并排比较不受干扰。

只需创建一个结构类型,其中包含特定于计算机的数据 (在最低的状态),并使用,而不是全局变量。

我已经很少做的原因只是因为我写的状态机的大部分已被单一实例类型 (一次性,在过程的开始,例如读取的配置文件),不需要运行多个实例。但如果您需要运行多个有价值。

巨人的开关组合使用 FSM 的代码。即使没有仅每切换函数调用,还有一些代码,并且可以很容易为人可以滥用,仅仅增加了小型四线过渡内联。当一个 10 行。然后,它获取失去控制。结构数组,使用 FSM 保持干净-您可以查看每个过渡和效果 (函数)。并且我开始当枚举在 ISO 的眼睛,twinkle 为 6809 嵌入式平台使用的编译器编写的代码,应我们说,少于理想:-)

您说得对,枚举会好一些,但我仍然更喜欢有一个结构数组 FSM。它是所有运行数据,而不是代码 (嗯,一些代码,但塞得满满了我给的 FSM 循环的可能性很小)。

提到使用 globals,共享数据,但它很可能是清洗器如果您定义的状态函数是int (*fn)(void*);其中void*是一个指针指向的数据,采用每个状态函数作为参数。然后状态函数可以使用这些数据或忽略它们。

这就是实际上是个好主意,@ldog,和它适合于更好的封装。我倾向于不做只是因为绝大多数的状态机已完成往往是单一元素类型和隔离在一个文件中 (因此,全局变量不真正全球)。通过使用您的方法,您可以拥有一段代码轻松地运行多个状态机,通过强制转换void*到每台计算机结构。

不同之处在于它从未向我介绍通配符状态写入 FSMs,使用相同的数据中的代码分离。这个主意很有意思 !但是,循环访问数组的过渡可能会变得昂贵,如果您有大量的状态 (这已对我来说这种情况,因为自动生成的 C 代码)。在这种情况下,它将每个状态中有一个数组转换效率更高。这样一种状态不再一个 enum 值,但转换表。这样一来,没有要循环访问的机器,但不仅仅是与当前状态相关的所有过渡。

其他答案很好,但曾用非常简单的状态机器时的非常"轻量"实现如下所示︰

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 ... */
    }
}

我将使用此状态机是非常简单的函数指针和状态转换表方法是一种浪费。这通常是用于逐字符或字的分析。

+ 1。对于简单的情况,我有实际使用此方法。

使用枚举以#def + 1.

您可能需要考虑状态机编译器http://smc.sourceforge.net/

这个漂亮的开放源代码工具接受简单的语言中的状态机的说明并编译它的任何一种十几多种语言,包括 C 和 c + +。实用程序本身编写的 Java,并且可以生成的一部分。

若要执行此操作,而不是样本的手动编码使用 GoF 状态模式或任何其他方法的原因是样本的状态机表示为代码,一旦基础结构往往在需要生成来支持它的重量下消失。使用此方法为您提供卓越任务分离,并且保持的状态机结构可见。自动生成的代码放入不需要接触,以便您可以返回并伪造状态机的结构而不会影响支持您编写的代码的模块中。

很抱歉,我是被过高的热情,,我肯定每个人都将。但它太热门的槽口设施和完备。

+ 1。没有任何问题与正在热情的工具,可以节省时间和精力。我仍然记得早期然后发现 yacc 使用 lex/手编制分析程序,用于编译器。前所未有的回之后。

Pardon me 的断裂在计算机科学中的每个规则,但状态机是一个 (我可以统计只有两只手关闭) 几更多 efficent,不仅使goto语句所在的位置您的代码更清晰且更易于读取。由于goto语句都基于标签,您可以命名您不必跟踪了乱七八糟的编号,或使用 enum 的状态。这也使得多简洁代码由于不需要所有额外才的函数指针或大的 switch 语句和循环时。我提到过效率会更高?

下面是一个状态机可能如下所示︰

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

您获得了大致的了解。关键是您可以在 efficent 中实现状态机的方法和一个可以相对容易地阅读,看状态机读卡器在 screams。请注意,是否您正在使用goto语句,您仍然必须小心,因为它很容易在这样做时的脚对自己进行拍摄。

当状态机中的顶层对象时才有效。其他一些对象的时刻是偶尔轮询 / 邮件发给,都需要有状态,可能坚持采用这种方法 (,或您需要使其更为复杂)

对于使用 goto 语句 + 1

这真的会强制您使用所有中抢先多任务处理,但最简单的情况。

这些 gotos 可以替换为函数调用。并且,如果探查器会告诉您您的程序淹没由于函数调用系统开销,然后您可以替换调用 gotos 根据需要。

由于调用堆栈仅清除出错时,只需将 gotos 替换为函数调用 @AbtinForouzandeh 会导致 stackoverflow。

请务必检查 Miro Samek (博客状态空间,网站状态机器和工具),其文章在C/c + + 用户日志是很好的工作。

该网站包含开源和商业许可证的状态机框架 (QP 框架)事件处理程序 (QEP)、 一个基本建模工具 (QM)和允许绘制状态机、 创建代码和调试这些跟踪工具 (QSpy)中的完整 (C/c + +) 实现。

指南 》 中全面说明在什么/原因的实现以及如何使用它,也是好材料的分层和有限状态机的基本原理的理解。

该网站还包含几个板级支持包使用与嵌入平台软件的链接。

您幽默转义我,但您的链接很有用。

修改以下您这根据问题的标题。

@jldupont︰ 我只是意味着它是更好地阐明。我现在已删除的不相关部分的我的答案。

我已经添加什么网站/书,期望让使用软件成功我自己;它是我的书货架上最佳簿。

链接或者死或指向似乎已更改其方向为嵌入式软件的网站主页。可以上state-machine.com/resources/articles.php,看到某些内容的状态与机器相关链接甚至有大多数都是死,但。这是一只很好的链接有︰ state-machine.com/resources/...

内容来源于Stack Overflow C state-machine design
请输入您的翻译

C state-machine design

确认取消