状态机教程

发布于 2024-08-03 06:23:54 字数 1539 浏览 6 评论 0原文

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

稍尽春風 2024-08-10 06:23:54

如果使用函数指针,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() 函数,因为它很简单。

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

State machines are very simple in C if you use function pointers.

Basically you need 2 arrays - one for state function pointers and one for state transition rules. Every state function returns the code, you lookup state transition table by state and return code to find the next state and then just execute it.

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

I don't put lookup_transitions() function as it is trivial.

That's the way I do state machines for years.

楠木可依 2024-08-10 06:23:54

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

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

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

I prefer using function pointers over gigantic switch statements, but in contrast to qrdl's answer I normally don't use explicit return codes or transition tables.

Also, in most cases you'll want a mechanism to pass along additional data. Here's an example state machine:

#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);
}
ペ泪落弦音 2024-08-10 06:23:54

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

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

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

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

switch 基于语句的状态机通常使用一组宏来“隐藏”< code>switch 语句(或使用一组 if/then/else 语句代替 switch) 并制作相当于“FSM 语言”的内容,用于在 C 源代码中描述状态机。我个人更喜欢基于表格的方法,但这些方法肯定有优点,被广泛使用,并且对于更简单的 FSM 来说尤其有效。

Steve Rabin 在 《游戏编程瑰宝》第 3.0 章(设计通用的鲁棒 AI 引擎)

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

如果您也对 C++ 状态机实现感兴趣还有很多东西可以找到。如果您有兴趣,我会发布指示。

Unfortunately, most of the articles on state machines are written for C++ or other languages that have direct support for polymorphism as it's nice to model the states in an FSM implementation as classes that derive from an abstract state class.

However, it's pretty easy to implement state machines in C using either switch statements to dispatch events to states (for simple FSMs, they pretty much code right up) or using tables to map events to state transitions.

There are a couple of simple, but decent articles on a basic framework for state machines in C here:

Edit: Site "under maintenance", web archive links:

switch statement-based state machines often use a set of macros to 'hide' the mechanics of the switch statement (or use a set of if/then/else statements instead of a switch) and make what amounts to a "FSM language" for describing the state machine in C source. I personally prefer the table-based approach, but these certainly have merit, are widely used, and can be effective especially for simpler FSMs.

One such framework is outlined by Steve Rabin in "Game Programming Gems" Chapter 3.0 (Designing a General Robust AI Engine).

A similar set of macros is discussed here:

If you're also interested in C++ state machine implementations there's a lot more that can be found. I'll post pointers if you're interested.

等风来 2024-08-10 06:23:54

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

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

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

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

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

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

State machines are not something that inherently needs a tutorial to be explained or even used. What I suggest is that you take a look at the data and how it needs to be parsed.

For example, I had to parse the data protocol for a Near Space balloon flight computer, it stored data on the SD card in a specific format (binary) which needed to be parsed out into a comma seperated file. Using a state machine for this makes the most sense because depending on what the next bit of information is we need to change what we are parsing.

The code is written using C++, and is available as ParseFCU. As you can see, it first detects what version we are parsing, and from there it enters two different state machines.

It enters the state machine in a known-good state, at that point we start parsing and depending on what characters we encounter we either move on to the next state, or go back to a previous state. This basically allows the code to self-adapt to the way the data is stored and whether or not certain data exists at all even.

In my example, the GPS string is not a requirement for the flight computer to log, so processing of the GPS string may be skipped over if the ending bytes for that single log write is found.

State machines are simple to write, and in general I follow the rule that it should flow. Input going through the system should flow with certain ease from state to state.

眸中客 2024-08-10 06:23:54

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

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

This is all you need to know.

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;
    }
}
江南烟雨〆相思醉 2024-08-10 06:23:54

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

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

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

There is a lot of lesson to learn handcrafting state machines in C, but let me also suggest Ragel state machine compiler:

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

It has quite simple way of defining state machines and then you can generate graphs, generate code in different styles (table-driven, goto-driven), analyze that code if you want to, etc. And it's powerful, can be used in production code for various protocols.

爱她像谁 2024-08-10 06:23:54

实时面向对象建模非常棒(1994 年出版,现在出售仅需 81 美分,另加 3.99 美元运费)。

Real-Time Object-Oriented Modeling was fantastic (published in 1994 and now selling for as little as 81 cents, plus $3.99 shipping).

孤单情人 2024-08-10 06:23:54

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

State machines can be very complex for a complex problem. They are also subject to unexpected bugs. They can turn into a nightmare if someone runs into a bug or needs to change the logic in the future. They are also difficult to follow and debug without the state diagram. Structured programming is much better (for example you would probably not use a state machine at mainline level). You can use structured programming even in interrupt context (which is where state machines are usually used). See this article "Macros to simulate multi-tasking/blocking code at interrupt level" found at codeproject.com.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文