C状态机设计


193

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

我想知道您是否对SO的专家会分享您的状态机设计技术。

注意:我主要是在尝试和测试过实现技术之后。

更新:基于SO收集的所有宝贵意见,我决定采用以下架构:

事件泵指向事件集成器,事件集成器指向调度程序。 调度程序指向1到n的操作,这些操作返回事件集成器。 带有通配符的转换表指向调度程序。


4
这里的答案非常好。还要看看这个重复的问题,它也有几个很好的答案:stackoverflow.com/questions/1371460/state-machines-tutorials
Michael Burr



Answers:


170

我之前设计的状态机(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,而不是真正的全球性)。与所有全局变量一样,它需要小心。

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

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也可以类似地工作,从而允许处于特定状态的任何事件调用函数。

它还可以确保,如果您到达transitions数组的末尾,则会收到一条错误消息,指出您的FSM尚未正确构建(通过ST_ANY/EV_ANY组合使用)。

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

毫无疑问,如今会有更高层次的抽象可能更适合,但我怀疑它们都将归结为这种相同的结构。


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

只需创建一个结构类型来保存特定于机器的数据(至少要声明状态),然后使用它而不是全局变量即可。

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


24
一个巨大的开关将代码与FSM混合在一起。即使每个转换只有一个函数调用,也仍然会有一些代码,对于某人来说,只需添加一个小的4行转换内联就很容易滥用它。母鸡十行。然后它失控了。使用struct数组,FSM保持干净-您可以看到每个过渡和效果(功能)。我从枚举在ISO眼中开始转瞬即逝,开始为6809嵌入式平台编写代码,但编译器还不够完美:-)
paxdiablo,2009年

5
没错,枚举会更好,但我仍然更喜欢将FSM作为结构数组。然后,它们全部由数据而不是代码运行(嗯,虽然有一些代码,但是填充我给出的FSM循环的机会很小)。
paxdiablo

2
很好,对于过程控制状态机,我过去总是为每个状态添加三个(可能为空)子状态,因此对状态函数的调用将变为GotKey(substate),其中子状态为:-SS_ENTRY-SS_RUN-SS_EXIT基本上,状态函数在进入时使用SS_ENTRY子状态进行调用,以便该状态可以重建状态(例如,执行器位置)。没有过渡时,将传递SS_RUN子状态值。转换后,将使用SS_EXIT子状态调用状态函数,以便可以进行任何清理(例如,释放资源)。
Metiu

13
您提到使用全局变量共享数据,但是如果将状态函数定义为int (*fn)(void*);where,void*则指向每个状态函数作为参数接受的数据的指针可能会更干净。然后,状态函数可以使用数据,也可以忽略它们。
ldog 2011年

13
我使用相同的数据/代码分隔来编写FSM,只是我从来没有想到引入“通配符”状态。有趣的主意!但是,如果您有很多状态,则迭代转换数组可能会变得很昂贵(对我来说就是这种情况,因为C代码是自动生成的)。在这种情况下,每个状态具有一组转换将更为有效。因此,状态不再是枚举值,而是过渡表。这样,您不必遍历机器中的所有过渡,而只需遍历与当前状态相关的过渡即可。
Frerich Raabe 2011年

78

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

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

当状态机非常简单,以至于函数指针和状态转换表的方法过大时,我会使用它。这对于逐个字符或逐个单词的解析通常很有用。


37

请原谅我打破了计算机科学中的每条规则,但是状态机是为数不多的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语句,则仍必须小心,因为这样做很容易使自己陷入困境。


4
仅当状态机在顶级对象中时,这才起作用。当某些其他对象偶尔被轮询/发送消息到需要状态时,您就会被这种方法所困扰(否则您必须使其变得更加复杂)
skrebbel

1
实际上,这迫使您在最简单的情况下都使用抢占式多任务处理。
Craig McQueen

1
那些goto可以替换为函数调用。而且,如果探查器告诉您程序由于函数调用开销而淹没,则可以根据需要将调用替换为gotos。
Abtin Forouzandeh 2012年

7
@AbtinForouzandeh只是用函数调用替换了goto会导致堆栈溢出,因为仅在发生错误的情况下才清除调用堆栈。
JustMaximumPower 2012年

我同意goto方法。这是一组宏来说明这一点。宏使您的代码结构化,就像您正常编码一样。它也可以在中断水平,这通常是其中状态机需要codeproject.com/Articles/37037/...
eddyq

30

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

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

这样做的原因,而不是使用GoF状态模式或任何其他方法进行手工编码,是因为一旦将状态机表示为代码,则在需要生成支持它的样板的负担下,基础结构往往会消失。使用这种方法可以很好地将关注点分离,并使状态机的结构保持“可见”。自动生成的代码会进入您不需要接触的模块,因此您可以回头查看状态机的结构,而不会影响您编写的支持代码。

抱歉,我太热情了,毫无疑问让所有人失望。但这是一流的实用程序,并且文档齐全。


20

一定要检查Miro Samek的工作(博客State Space,网站State Machines&Tools),他在C / C ++用户杂志上的文章很棒。

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

本书对实现的什么/为什么以及如何使用它进行了广泛的解释,并且是了解层次结构和有限状态机基础知识的重要材料。

该网站还包含指向多个板级支持程序包的链接,以在嵌入式平台上使用该软件。


根据您的双关语,我修改了问题的标题。
jldupont

@jldupont:我只是想澄清一下。我已经删除了答案中不相关的部分。
丹尼尔·达拉纳斯

1
我自己成功使用了该软件,然后在网站/书上添加了期望的内容;这是我书架上最好的书。
Adriaan

@Adriann,很好的解释!我刚刚修改了网站的首页,以前的链接已停止工作。
Daniel Daranas 2011年

2
链接已失效或指向该网站的主页,该网站似乎已将其方向更改为嵌入式软件。您仍然可以在state-machine.com/resources/articles.php上看到一些内容,但是即使在大多数与状态机相关的链接上,它们也已失效。这是唯一的好链接之一有:state-machine.com/resources/...
塔蒂亚娜Racheva

11

我做了与paxdiablo描述的操作类似的操作,只是设置了一个二维的函数指针数组,而不是状态/事件转换数组,其中事件值是一个轴的索引,而当前状态值是另一个。然后我打电话给我state = state_table[event][state](params),正确的事情发生了。当然,表示无效状态/事件组合的单元格会获得指向该函数的指针。

显然,这仅在状态和事件值均为连续范围且从0开始或足够接近时才有效。


1
这种解决方案的感觉无法很好地扩展:桌子上的东西太多了,不是吗?
jldupont

2
+1。这里的扩展问题是内存-我自己的解决方案有一个扩展问题,即重新扫描过渡表所花费的时间(尽管您可以手动优化最常见的过渡)。这是为了速度而牺牲的内存-这只是一个权衡。您可能需要检查边界,但这并不是一个不好的解决方案。
paxdiablo

伙计们-我的评论未按预期发表:我的意思是,这更加费力且容易出错。如果添加状态/事件,则需要进行大量编辑。
jldupont

3
没有人说二维数组是手工初始化的。也许有些东西可以读取配置文件并创建它(或者至少可以存在)。
约翰·兹温克

初始化数组的一种方法是利用预处理器作为后期绑定器(与早期绑定相反)。您定义了所有状态的列表#define STATE_LIST() \STATE_LIST_ENTRY(state1)\STATE_LIST_ENTRY(state2)\...(每个状态后都有一个隐含的换行符\ ),您在其中使用STATE_LIST宏(重新)定义了入口宏。示例-制作状态名称数组:#define STATE_LIST_ENTRY(s) #s , \n const char *state_names[] = { STATE_LIST() };\n #undef STATE_LIST_ENTRY。首先需要进行一些设置,但这功能非常强大。添加新状态->保证没有遗漏。
hlovdal19年

9

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

由于本文中没有指向完整代码下载的链接,因此我可以自由地将代码粘贴到项目中并进行检查。下面的内容经过测试,包括一些较小但几乎显而易见的缺失部分。

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

如果您可以在没有丢失任何内容的情况下生存,那么这是Miro Samek实现的一种非常不错的替代方法-尽管它正确地实现了UML语义,但与完整的UML Statechart实现相距甚远,而Samek的设计代码无法处理退出/转换/ entry动作顺序正确。

如果此代码可以满足您的需要,并且您的系统具有不错的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)

嗯...您的代码中缺少某事。首先,您包括两个标题,但仅提供第一个。当我只注释“ include”语句时,在编译时会出现以下错误:d:\ 1 \ hsm> g ++ test.cpp test.cpp:195:1:错误:“静态无效化CompState <H,id,B>的特殊化” :: init(H&)[H = TestHSM; unsigned int id = 0u; B =实例化后的CompState <TestHSM,0u,TopState <TestHSM>>]'
Freddie Chopin

我必须将所有HSMINIT()的定义移到TestHSM类之上,并且它可以编译并正常工作(;唯一错误的事实是,所有过渡都是“外部的”,而它们应该是“内部的”-文章中对此进行了一些辩论,作者认为“肾外”是正确的,但所用箭头表示“内部”
弗雷迪·肖邦

5

我喜欢状态机(至少用于程序控制)的技术是使用函数指针。每个状态由不同的功能表示。该函数采用输入符号,并返回下一个状态的函数指针。中央调度循环监视器获取下一个输入,将其输入到当前状态,并处理结果。

它的类型有些奇怪,因为C没有办法表明返回自己的函数指针的类型,所以状态函数return 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);
    }
 }

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


+1确实很棒,并为过渡功能内的手动功能提供了不错的地方
Fire Crow

5

最简单的情况

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

要点:状态是私有的,不仅对编译单元而且对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()之前的(无效)括号)时,我忘记了显式丢弃下一个状态。即使编译器默默地接受了遗漏,我还是喜欢这样做。它告诉代码的读者“是的,我确实的意思是在不使用返回值的情况下调用该函数”,并且它可能阻止静态分析工具对其发出警告。这可能是特质的,因为我不记得曾经见过其他人这样做。

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

状态处理程序如下所示:

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语句超出了一个完整的屏幕,我也更喜欢它的清晰度和可读性。对于更复杂的情况,表格会很快失去控制,就像其他人指出的那样。我在这里介绍的惯用语使您可以在需要时添加一系列事件和状态,而不必维护消耗内存的表(即使它可能是程序内存)。

免责声明

特殊需求可能会使这些成语的用处不大,但我发现它们非常清楚且易于维护。


我会避免仅将“ this”用作关联的变量名或符号,即使它实际上不是保留字也是如此。
XTL 2012年

4

经过极其严格的测试,但对代码很有趣,现在它的版本比我原来的答案更精致。最新版本可以在mercurial.intuxication.org中找到:

短信

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

14
我喜欢“未经测试”的评论。似乎表明存在一定程度的未经测试,并且您花了很多精力进行未测试:-)
paxdiablo,2009年

@Christoph此答案中的链接已断开。另外,您是否测试过此代码?如果它已经过测试并且可以工作,则应从答案中删除它。也可能会显示一个示例,说明宏扩展后会产生什么代码。我喜欢一般的想法。
Joakim

4

我真的很喜欢paxdiable的答案,并决定为我的应用程序实现所有缺少的功能,例如保护变量和特定于状态机的数据。

我将自己的实施上传到此站点,以便与社区共享。已使用IAR Embedded Workbench for ARM进行了测试。

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


在2018年找到了这一点并且它仍然适用。我正在阅读@paxdiablo答案,并且之前我已经在嵌入式系统中成功使用过这种类型的实现。此解决方案增加了paxdiablos答案中缺少的内容:)
Kristoffer

4

另一个有趣的开源工具是statecharts.org上的Yakindu Statechart Tools。它利用Harel状态图,从而提供分层和并行状态,并生成C和C ++(以及Java)代码。它不使用库,而是遵循“普通代码”方法。该代码基本上适用于开关盒结构。代码生成器也可以定制。此外,该工具还提供许多其他功能。


3

来得这么晚(与往常一样),但是扫描到目前为止的答案,我认为缺少了一些重要的东西。

我在自己的项目中发现它对 没有针对每个有效状态/事件组合的功能。我喜欢有效地具有状态/事件的2D表的想法。但是我喜欢表元素不仅仅是一个简单的函数指针。取而代之的是,我尝试组织我的设计,因此它的核心是一堆简单的原子元素或动作。这样,我可以在状态/事件表的每个交点处列出那些简单的原子元素。这个想法是你定义大量的N个平方(通常非常简单)的函数。为什么要这么容易出错,费时,难写,难读呢?

我还为表中的每个单元格包括一个可选的新状态和一个可选的函数指针。在某些特殊情况下,您不希望仅触发一系列原子动作的情况下使用函数指针。

您知道当您可以表达许多不同的功能(仅通过编辑表而无需编写新的代码)时,您就做对了。


2
也许一个例子会很好,不是吗?
jldupont

1
可以单独提出的一个现实示例是一项具有挑战性的任务,它将需要比我现在准备的更多的时间。我的帖子中有什么特别难以理解的内容吗?也许我可以更清楚地表达它。这个想法很简单;不要为每个事件/状态组合定义需要单独功能的状态机制,那样会获得太多功能。而是至少在大多数情况下,找到另一种描述事件/状态组合所需功能的方法。
比尔·福斯特

2
理解:伪代码示例会很好,但是您的意思很清楚。
jldupont

3

Alrght,我认为我的与其他所有人的有所不同。代码和数据的分离比我在其他答案中看到的要多一些。我确实阅读了编写该理论的理论,该理论实现了完整的正则语言(遗憾的是没有正则表达式)。厄尔曼,明斯基,乔姆斯基。不能说我完全理解,但是我是尽可能直接地从老大师那里汲取教训的:通过他们的话。

我使用一个指向谓词的函数指针,该谓词确定了向“是”状态或“否”状态的转换。这有助于为您以更类似于汇编语言的方式编程的常规语言创建有限状态接受器。请不要因为我愚蠢的名字选择而被拖延。'czek'=='检查'。'grok'== [在Hacker字典中查找]。

因此,对于每次迭代,czek都以当前字符作为参数调用谓词函数。如果谓词返回true,则使用字符(指针前进),然后我们按照“ y”转换选择下一个状态。如果谓词返回false,则不使用该字符,并且我们遵循'n'转换。因此,每条指令都是双向分支!当时我一定在读《梅尔的故事》。

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

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

2
这就是任何解析器或词法分析器生成器都会为您高兴地发出的信息。简直如此。是否要手动编写代码值得怀疑。当然,它具有教学上的优点。
恢复莫妮卡



2

在某个地方看到了

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

1
这很有趣,但是在您给出一个或两个示例(也许是去宏的结果)或讨论为什么这可能比另一个更实用之前,您是不会赞成的。有趣的使用孤立的方括号和宏。我认为可以对执行某种尾部递归优化的语言执行类似的操作;您可以使用直接函数调用,而不必担心函数调用垃圾会使堆栈空间超载(我认为这是宏在这里基本克服的问题)
Ape-in​​ago

2
这种方法的优点是...?我看到了一些缺点,例如混淆了宏,并且使用goto它们会导致对抢先的多任务OS的依赖。
Craig McQueen

2

鉴于您暗示您可以使用C ++,因此可以使用OO代码,我建议您评估“ GoF”状态模式(GoF =四人帮,这是编写设计模式书的人,使设计模式成为众人瞩目的焦点)。

它不是特别复杂,并且已被广泛使用和讨论,因此很容易看到示例和在线解释。

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

如果担心效率,则值得进行实际基准测试,以确保非OO方法更有效,因为很多因素都会影响性能,并且并非总是OO不好,功能代码就好。同样,如果内存使用量对您来说是一个约束,那么再次值得进行一些测试或计算,以查看如果您使用状态模式,这对于您的特定应用程序实际上是否是一个问题。

正如Craig所建议的,以下是一些与“ Gof”状态模式的链接:


看起来更像是一条评论:我可以建议您这样对待吗?即不要将其放在“答案”部分。
2011年

如果您可以为“ GoF状态模式”提供一个好的URL链接,对于那些不熟悉它的人,那将是很好的。
Craig McQueen

1
@jldupont-公平的评论。根据我的个人经验,我更改了文字以使其成为正确的答案,除非有特定的性能问题,否则GoF方法可以正常工作,并且将具有相对较大的“用户群”
-Mick

@Craig-添加了一些链接。在我添加它们时,两者看上去都很准确清晰。
米克,

2

这是使用消息队列作为事件的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

1

您的问题很笼统,
以下是两篇可能有用的参考文章,

  1. 嵌入式状态机实现

    本文介绍了一种为嵌入式系统实现状态机的简单方法。出于本文的目的,状态机被定义为可以处于少数状态之一的算法。状态是导致输入到输出以及输入到下一状态的规定关系的条件。
    精明的读者会很快注意到本文中描述的状态机是Mealy机。与Moore机器相反,Mealy机器是一种状态机,其中输出是当前状态和输入的函数,而Moore机器中,输出仅是状态的函数。

    • C和C ++中的编码状态机

      我在本文中专心研究状态机基础知识和一些简单的编程准则,以C或C ++编写状态机。我希望这些简单的技术可以变得更加普遍,以便您(和其他人)可以从源代码中轻松看到状态机结构。



1

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

这个例子基于我已经读过的其他方法。它不使用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)

生成的输出标头包含typedef:

/* 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 *);
  • 枚举eFsmTurnstileCheck用于确定转换是否被阻止EFSM_TURNSTILE_TR_RETREAT,允许进行转换EFSM_TURNSTILE_TR_ADVANCE或函数调用之前是否没有转换EFSM_TURNSTILE_TR_CONTINUE
  • 枚举 eFsmTurnstileState只是状态列表。
  • 枚举 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);
}

在我看来,所有标头业务以及所有具有简单快速接口的功能都是值得的。


0

您可以使用开源库OpenFST

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


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

您可以通过使用指向函数的常量函数指针数组来进一步优化其安全性
AlphaGoku

0

我个人将自引用结构与指针数组结合使用。我在github上上传了一个教程,链接:

https://github.com/mmelchger/polling_state_machine_c

注意:我确实意识到该线程已经很老了,但是我希望对状态机的设计有所投入并有所想法,并希望能够为使用C语言进行状态机设计提供示例。


0

您可以考虑使用UML-state-machine-in-c,它是C语言中的“轻量级”状态机框架。我编写了此框架,以同时支持有限状态机分层状态机。。与状态表或简单切换案例相比,框架方法具有更高的可伸缩性。它可以用于简单的有限状态机到复杂的分层状态机。

状态机由state_machine_t结构表示。它仅包含两个成员“事件”和一个指向“ 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


-1

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

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

此方法使用回调函数在中断的每个函数中进行拾取。每个功能都包含每个功能所独有的状态列表。中央“空闲循环”用于运行状态机。“空闲循环”不知道状态机如何工作,而是各个功能“知道要做什么”。为了编写这些功能的代码,只需创建一个状态列表,然后使用宏“暂停”和“恢复”即可。当我为Nexus 7000交换机编写收发器库时,我在思科使用了这些宏。

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.