C durum-makine tasarımı [kapalı]


193

Karışık C ve C ++ ile küçük bir proje hazırlıyorum. İş parçacığımın kalbinde küçük bir devlet makinesi yapıyorum.

SO'daki guruların devlet makinesi tasarım tekniklerinizi paylaşıp paylaşmayacağını merak ediyordum.

NOT: Ben öncelikle denenmiş ve test edilmiş uygulama tekniklerinden sonra duyuyorum.

GÜNCELLENDİ: SO'da toplanan tüm harika girdilere dayanarak, bu mimariye yerleştim:

Bir olay pompası, bir dağıtım programına işaret eden bir olay entegratörünü işaret eder.  Dağıtım programı, olay entegratörünü gösteren 1'den n'ye kadar eylemleri gösterir.  Joker karakterli bir geçiş tablosu dağıtım programına işaret eder.


4
Buradaki cevaplar çok iyi. Ayrıca birkaç iyi yanıtı olan bu yinelenen soruya da bakın: stackoverflow.com/questions/1371460/state-machines-tutorials
Michael Burr



Ayrıca bu soruya bakın .
Daniel Daranas

Yanıtlar:


170

Daha önce tasarladığım durum makinelerinin (C, C ++ değil) hepsi bir structdizi ve döngüye düştü . Yapı temel olarak bir durum ve olaydan (arama için) ve yeni durumu döndüren bir işlevden oluşur, örneğin:

typedef struct {
    int st;
    int ev;
    int (*fn)(void);
} tTransition;

Ardından durumlarınızı ve olaylarınızı basit tanımlarla tanımlarsınız ( ANYbunlar özel işaretçilerdir, aşağıya bakın):

#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

Sonra geçişler tarafından çağrılan tüm işlevleri tanımlarsınız:

static int GotKey (void) { ... };
static int FsmError (void) { ... };

Tüm bu işlevler değişken almamak ve durum makinesi için yeni durumu döndürmek için yazılmıştır. Bu örnekte, genel değişkenler herhangi bir bilgiyi gerektiğinde durum işlevlerine aktarmak için kullanılır.

FSM genellikle tek bir derleme birimi içinde kilitlendiğinden ve tüm değişkenler bu birim için statik olduğundan, globalleri kullanmak göründüğü kadar kötü değildir (bu yüzden yukarıdaki "global" etrafında tırnak kullandım - bunlar daha fazla paylaşılıyor FSM, gerçekten küresel olmaktan çok). Tüm küresellerde olduğu gibi, bakım gerektirir.

Daha sonra geçişler dizisi tüm olası geçişleri ve bu geçişler için çağrılan işlevleri (tümünü yakala sonuncusu dahil) tanımlar:

tTransition trans[] = {
    { ST_INIT, EV_KEYPRESS, &GotKey},
    : :
    { ST_ANY, EV_ANY, &FsmError}
};
#define TRANS_COUNT (sizeof(trans)/sizeof(*trans))

Bunun anlamı şudur: ST_INITeyaletteyseniz ve EV_KEYPRESSolayı alıyorsanız , telefon edin GotKey.

Daha sonra FSM'nin çalışmaları nispeten basit bir döngü haline gelir:

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

Yukarıda belirtildiği gibi, ST_ANYjoker karakter olarak kullanıldığına dikkat edin ve olayın mevcut durum ne olursa olsun bir işlevi çağırmasına izin verin. EV_ANYbenzer şekilde çalışır ve belirli bir durumdaki herhangi bir olayın bir işlevi çağırmasına izin verir.

Geçiş dizisinin sonuna ulaşırsanız, FSM'nizin doğru şekilde oluşturulmadığını belirten bir hata alacağınızı da garanti edebilirsiniz ( ST_ANY/EV_ANYkombinasyonu kullanarak) .

Bunun için benzer kod kullandım, örneğin iletişim yığınlarının ve gömülü sistemler için protokollerin erken uygulanması gibi birçok iletişim projesinde. En büyük avantajı, geçişler dizisini değiştirmedeki basitliği ve göreceli kolaylığıydı.

Kuşkusuz, bugünlerde daha uygun olabilecek daha yüksek seviyeli soyutlamalar olacak, ancak hepsinin aynı tür yapıya kadar kaybolacağından şüpheleniyorum.


ldogBir yorumdaki durumlar olarak , tüm işlevlere bir yapı işaretçisi geçirerek (ve olay döngüsünde bunu kullanarak) globallerden tamamen kaçınabilirsiniz. Bu, çoklu durum makinelerinin parazit olmadan yan yana çalışmasına izin verecektir.

Sadece makineye özgü verileri tutan (minimum düzeyde durum) bir yapı tipi oluşturun ve bunu globaller yerine kullanın.

Nadiren yapmamın nedeni, yazdığım durum makinelerinin çoğunun birden fazla örnek çalıştırması gerekmeyen tekli türler (bir kerelik, işlem başlangıcında, yapılandırma dosyası okuması) olması. . Ancak birden fazla çalıştırmanız gerekiyorsa değeri vardır.


24
Dev bir anahtar, kodu FSM ile karıştırır. Geçiş başına yalnızca bir işlev çağrısı olsa bile, hala bazı kodlar vardır ve birisinin 4 satırlık küçük bir geçiş satır içi ekleyerek bunu kötüye kullanması kolaydır. on satırlık bir tavuk. Sonra kontrolden çıkar. Struct array ile FSM temiz kalır - her geçişi ve efekti (işlevi) görebilirsiniz. Ve enumlar ISO'nun gözünde bir parıltı olduğunda başladım, deriz ki, mükemmel olandan daha az olan derleyiciler içeren 6809 gömülü platformlar için kod yazıyorum :-)
paxdiablo

5
Haklısın, sıralamalar daha iyi olurdu, ama yine de FSM'i bir yapı dizisi olarak tercih ediyorum. Sonra hepsi kod yerine veri ile çalışır (iyi, bazı kodlar var ama verdiğim FSM döngüsünü doldurma şansı zayıf).
paxdiablo

2
Bu durum iyidir, işlem kontrol durumu makineleri için her durum için her zaman üç (muhtemelen boş) alt bölge eklerdim, böylece bir durum işlevi çağrısı GotKey (alt durum) olur ve alt durum şöyle olur: - SS_ENTRY - SS_RUN - SS_EXIT Temel olarak, durum işlevi girişte bir SS_ENTRY alt durumu ile çağrılır, böylece durum bir durumu yeniden oluşturabilir (örn. Aktüatör pozisyonları). Geçiş olmasa da, SS_RUN alt durum değeri geçer. Geçişten sonra, durum işlevi SS_EXIT alt durumu ile çağrılır, böylece herhangi bir temizleme (ör. Deallocate kaynakları) yapabilir.
Metiu

13
Globals kullanarak veri paylaştığınızdan bahsettiniz, ancak durum işlevlerini , her durum işlevinin parametre olarak aldığı verilere işaretçi olduğu int (*fn)(void*);yerde tanımlamanız büyük olasılıkla daha temiz olacaktır void*. Ardından durum işlevleri verileri kullanabilir veya yoksayabilir.
ldog

13
FSM yazmak için aynı veri / kod ayrımını kullanıyorum, ancak 'joker karakter' durumlarını tanıtmak için bana hiç gelmedi. İlginç fikir! Ancak, çok fazla durumunuz varsa geçiş dizisini yinelemek pahalı olabilir (C kodu otomatik olarak oluşturulduğundan beri benim için durum buydu). Bu gibi durumlarda, durum başına bir dizi geçişe sahip olmak daha verimli olacaktır. Yani bir durum artık bir enum değeri değil, bir geçiş tablosu. Bu şekilde, makinedeki tüm geçişleri değil, sadece mevcut durumla ilgili olanları tekrarlamanız gerekmez .
Frerich Raabe

78

Diğer cevaplar iyi, ama devlet makinesi çok basit olduğunda kullandığım çok "hafif" bir uygulama şöyle:

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

Durum makinesi, işlev işaretçisi ve durum geçiş tablosu yaklaşımı aşırıya kaçacak kadar basit olduğunda bunu kullanırım. Bu genellikle karakter karakter veya sözcük sözcük ayrıştırma için kullanışlıdır.


37

Bilgisayar bilimindeki her kuralı çiğnediğim için affedin, ancak bir devlet makinesi, goto ifadenin sadece daha verimli olmadığı, aynı zamanda kodunuzu daha temiz ve okunmasını kolaylaştıran . Çünkü gotoifadeleri etiketlere dayalı, bunun yerine sayıların bir karmaşa izlemek ya da bir enum kullanmak zorunda senin durumlarını adlandırabilirsiniz. Ayrıca, fonksiyon işaretleyicilerinin veya devasa anahtar ifadelerinin ekstra döngüsüne ve döngülere ihtiyaç duymadığınız için çok daha temiz bir kod sağlar. Ben de daha verimli olduğunu söylemiş miydim?

Bir durum makinesi şöyle görünebilir:

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

Genel fikri anlıyorsunuz. Mesele şu ki, devlet makinesini verimli bir şekilde ve okunması nispeten kolay olan ve okuyucuya bir devlet makinesine baktıklarını çığlık atan bir yöntem uygulayabilirsiniz. gotoİfadeler kullanıyorsanız , yine de dikkatli olmalısınız, çünkü bunu yaparken kendinizi ayağınızla vurmak çok kolaydır.


4
Bu yalnızca durum makinesi en üst düzey nesnedeyse çalışır. Zaman zaman sorgulanan / mesaj gönderen başka bir nesnenin durumu olması gerektiği anda, bu yaklaşıma takılı
kalıyorsunuz

1
Bu gerçekten sizi en basit durumlar dışında önleyici çoklu görev kullanmaya zorlar.
Craig McQueen

1
Bu gotolar işlev çağrılarıyla değiştirilebilir. Bir profil oluşturucu, işlev çağrısı yükü nedeniyle programınızın boğulduğunu söylüyorsa, çağrıları gerektiği gibi gotolarla değiştirebilirsiniz.
Abtin Forouzandeh

7
@AbtinForouzandeh, işlev çağrılarıyla sadece gotoları değiştirmek, çağrı yığını sadece bir hata durumunda temizlendiğinden yığın akışına neden olur.
JustMaximumPower

Goto yöntemine katılıyorum. İşte bunu gösteren bir makro seti. Makrolar da kodunuzu normalde yaptığınız gibi kodlamış gibi yapılandırır. Ayrıca genellikle devlet makinelerine ihtiyaç duyulan kesme seviyesinde çalışır codeproject.com/Articles/37037/…
eddyq

30

Devlet Makine Derleyicisini http://smc.sourceforge.net/ olarak düşünebilirsiniz.

Bu görkemli açık kaynak yardımcı programı, bir devlet makinesinin açıklamasını basit bir dilde kabul eder ve C ve C ++ dahil olmak üzere bir düzine dilden birine derler. Yardımcı programın kendisi Java ile yazılmıştır ve bir yapının parçası olarak dahil edilebilir.

Bunu yapmanın nedeni, GoF Durum modeli veya başka bir yaklaşım kullanarak el kodlaması yerine, durum makineniz kod olarak ifade edildiğinde, alttaki yapı, onu desteklemek için oluşturulması gereken kazan plakasının ağırlığı altında kaybolma eğilimindedir. Bu yaklaşımı kullanmak, endişelerin mükemmel bir şekilde ayrılmasını sağlar ve devlet makinenizin yapısını 'görünür' tutarsınız. Otomatik oluşturulan kod, dokunmanız gerekmeyen modüllere girer, böylece yazdığınız destek kodunu etkilemeden devlet makinesinin yapısıyla geri dönebilir ve kemanlayabilirsiniz.

Üzgünüm, çok hevesli ve şüphesiz herkesi erteliyorum. Ama birinci sınıf bir yardımcı programdır ve iyi belgelenmiştir.


20

C / C ++ Users Journal'daki makaleleri harika olan Miro Samek'in (blog State Space , web sitesi State Machines & Tools ) çalışmalarını kontrol ettiğinizden emin olun .

Web sitesi, bir durum makinesi çerçevesinin (QP Framework) , bir olay işleyicinin (QEP) , temel bir modelleme aracının (QM) ve bir izleme aracının (QSpy) hem açık kaynak hem de ticari lisansında tam (C / C ++) bir uygulama içerir. durum makineleri çizmeye, kod oluşturmaya ve hata ayıklamaya izin ver.

Kitap, uygulamanın ne / neden ve nasıl kullanılacağına dair kapsamlı bir açıklama içeriyor ve ayrıca hiyerarşik ve sonlu durum makinelerinin temellerini anlamak için harika bir materyal.

Web sitesi ayrıca, yazılımın gömülü platformlarla kullanımı için çeşitli pano destek paketlerine bağlantılar içerir.


Sorunun başlığını cinsten sonra değiştirdim.
jldupont

@jldupont: Açıklığa kavuşturmanın daha iyi olduğunu söyledim. Cevabımın alakasız kısımlarını şimdi sildim.
Daniel Daranas

1
Yazılımı başarıyla kullandım, web sitesinde / kitapta ne olacağını ekledim; kitap rafımdaki en iyi kitap.
Adriaan

@Adriann, harika bir açıklama! Web sitesinin evini yeni değiştirdim, önceki bağlantı çalışmayı bırakmıştı.
Daniel Daranas

2
Bağlantılar ya ölü ya da gömülü yazılıma yönünü değiştirmiş görünen sitenin ana sayfasını gösteriyor. Halen state-machine.com/resources/articles.php adresindeki içeriğin bir kısmını görebilirsiniz , ancak orada durumla ilgili makine bağlantılarının çoğu öldü. Buradaki tek iyi bağlantılardan biri: state-machine.com/resources/…
Tatiana Racheva

11

Ben paxdiablo açıklayanlara benzer bir şey yaptım, sadece bir durum / olay geçişleri dizisi yerine, bir eksen indeksi ve geçerli durum değeri olarak olay değeri ile 2 boyutlu fonksiyon işaretçileri dizisi kurdum diğeri. Sonra sadece ararım state = state_table[event][state](params)ve doğru olan şey olur. Geçersiz durum / olay kombinasyonlarını temsil eden hücreler, elbette böyle bir işleve işaret eder.

Açıkçası, bu yalnızca durum ve olay değerlerinin hem bitişik aralıklar olması hem de 0'dan başlaması veya yeterince yakın olması durumunda işe yarar.


1
Bu çözüm iyi ölçeklendirilmemiş gibi geliyor: çok fazla masa dolgusu, değil mi?
jldupont

2
+1. Buradaki ölçekleme sorunu hafızadır - kendi çözümümde bir zamanlama ölçeklendirme sorunu vardır, yani geçişler tablosunu taramak için harcanan zaman (en yaygın geçişler için manuel olarak optimize edebilirsiniz). Bu, hız için hafızayı feda ediyor - bu sadece bir değiş tokuş. Muhtemelen sınırlar için kontrole ihtiyacınız vardır, ancak bu kötü bir çözüm değildir.
paxdiablo

Çocuklar - Yorumum amaçlandığı gibi ortaya çıkmadı: Çok daha zahmetli ve hata eğilimli olduğu anlamına geliyordu. Bir durum / etkinlik eklerseniz, çok sayıda düzenleme yapılması gerekir.
jldupont

3
Kimse 2D dizinin elle başlatıldığını söylemedi. Belki de bir yapılandırma dosyasını okuyan ve onu oluşturan bir şey vardır (ya da en azından kesinlikle olabilir).
John Zwinck

Dizileri bu şekilde başlatmanın bir yolu, ön-işlemcinin geç bağlayıcı olması (erken bağlamanın aksine) kullanmaktır. STATE_LIST makrosunu kullandığınızda giriş makrosunu (yeniden) tanımladığınız tüm durumların #define STATE_LIST() \STATE_LIST_ENTRY(state1)\STATE_LIST_ENTRY(state2)\...(her birinden sonra ima edilen yeni satır \ ) bir listesini tanımlarsınız. Örnek - eyalet isimlerinin bir dizi yapım: #define STATE_LIST_ENTRY(s) #s , \n const char *state_names[] = { STATE_LIST() };\n #undef STATE_LIST_ENTRY. Bazıları önce kurulum için çalışır, ancak bu son derece güçlüdür. Yeni eyalet ekle -> garantili kaçırılmayan garanti.
hlovdal

9

Stefan Heinzmann tarafından makalesinde çok güzel bir şablon tabanlı C ++ durum makinesi "çerçevesi" verilmiştir .

Makalede tam bir kod indirme bağlantısı olmadığından, kodu bir projeye yapıştırma ve kontrol etme özgürlüğünü aldım. Aşağıdaki şeyler test edilmiştir ve birkaç küçük ama oldukça belirgin eksik parçayı içerir.

Buradaki en büyük yenilik, derleyicinin çok verimli kod üretmesidir. Boş giriş / çıkış eylemlerinin maliyeti yoktur. Boş olmayan giriş / çıkış işlemleri satır içine alınır. Derleyici ayrıca istatistik çizelgesinin eksiksiz olduğunu doğrulamaktadır. Eksik eylemler bağlantı hataları oluşturur. Yakalanmayan tek şey eksik Top::init.

Bu, Miro Samek'in uygulanmasına çok güzel bir alternatif, eğer eksik olan olmadan yaşayabiliyorsanız - bu, UML anlambilimini doğru bir şekilde uygulasa da, tam bir UML Statechart uygulamasından çok uzakken, Samek'in tasarım kodları çıkış / geçişi işlemez / giriş eylemleri doğru sırada.

Bu kod yapmanız gerekenler için çalışıyorsa ve sisteminiz için iyi bir C ++ derleyiciniz varsa, muhtemelen Miro'nun C / C ++ uygulamasından daha iyi performans gösterecektir. Derleyici sizin için düzleştirilmiş bir O (1) geçiş durumu makinesi uygulaması oluşturur. Montaj çıktısının denetimi, optimizasyonların istendiği gibi çalıştığını doğrularsa, teorik performansa yaklaşırsınız. En iyi bölüm: nispeten küçük, anlaşılması kolay kod.

#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

Test kodu aşağıdadır.

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

Hmm ... sth kodunuzda eksik. Her şeyden önce iki üstbilgi eklersiniz, ancak yalnızca ilk üstbilgiyi sağlarsınız. Sadece "include" ifadesini yorumladığımda derlerken bu hatayı alıyorum: d: \ 1 \ hsm> g ++ test.cpp test.cpp: 195: 1: hata: 'statik void CompState <H, id, B> uzmanlığı :: init (H &) [H = TestHSM ile; unsigned int id = 0u; B = CompState <TestHSM, 0u, TopState <TestHSM>>] ', somutlaştırıldıktan sonra
Freddie Chopin

TestHSM sınıfının üstünde olmak için tüm HSMINIT () tanımlarını taşımak zorunda kaldım ve derler ve iyi çalışır (; Yanlış olan tek şey, tüm geçişlerin "dış" olması, ancak "iç" olması gerektiğidir - makalede bu konuda bazı tartışmalar ve yazar "extrenal" doğru olduğuna karar verdi, ancak kullanılan oklar "iç" önermektedir.
Freddie Chopin

5

Durum makineleri için sevdiğim teknik (en azından program kontrolü için olanlar) fonksiyon işaretçileri kullanmaktır. Her durum farklı bir işlevle temsil edilir. İşlev bir giriş sembolü alır ve bir sonraki durum için işlev işaretçisini döndürür. Merkezi dağıtım döngüsü izleyicileri bir sonraki girdiyi alır, geçerli duruma besler ve sonucu işler.

Üzerinde yazma işlevi biraz garipleşir, çünkü C'nin geri dönen işlev işaretçisi türlerini göstermenin bir yolu yoktur, bu nedenle durum işlevleri geri döner void*. Ama böyle bir şey yapabilirsiniz:

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

Daha sonra, bireysel durum işlevleriniz uygun değeri işlemek ve döndürmek için girişlerini açabilir.


+1 bu gerçekten güzel ve geçiş işlevleri içinde el işlevselliği için güzel yerler sunuyor
Fire Crow

5

En basit dava

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

Puanlar: Durum yalnızca derleme birimine değil, event_handler'a da özeldir. Özel durumlar, gerekli görülen her türlü yapı kullanılarak ana şalterden ayrı olarak ele alınabilir.

Daha karmaşık vaka

Anahtar birkaç ekran dolduğunda büyüdüğünde, işlevi doğrudan aramak için bir durum tablosu kullanarak her durumu işleyen işlevlere bölün. Devlet hala olay işleyiciye özeldir. Durum işleyici işlevleri bir sonraki durumu döndürür. Gerekirse, bazı olaylar ana olay gidericide özel muamele görmeye devam edebilir. Ben devlet giriş ve çıkış ve belki devlet makine başlangıç ​​için sözde olayları atmak ister:

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

Sözdizimini çivilediğimden emin değilim, özellikle işlev işaretçileri dizisiyle ilgili. Bunların hiçbirini bir derleyici aracılığıyla çalıştırmadım. İnceleme üzerine, sözde olayları (state_handler ()) çağrılmadan önce (void) parantez işlerken bir sonraki durumu açıkça atmayı unuttuğumu fark ettim. Bu, derleyiciler ihmali sessizce kabul etse bile yapmaktan hoşlandığım bir şey. Bu kod okuyuculara "evet, gerçekten dönüş değerini kullanmadan işlevi çağırmak anlamına geldiğini" söyler ve statik analiz araçlarının bu konuda uyarılmasını durdurabilir. Kendine özgü olabilir, çünkü bunu yapan başka birini gördüğümü hatırlamıyorum.

Noktalar: küçük bir karmaşıklık eklemek (bir sonraki durumun geçerli olandan farklı olup olmadığını kontrol etmek), başka bir yerde yinelenen koddan kaçınabilir, çünkü durum işleyici işlevleri bir durum girildiğinde ve bırakıldığında meydana gelen sözde olayların keyfini çıkarabilir. Durum işleyicilerinin sonucu işlenirken durumun değişemeyeceğini unutmayın, çünkü durum işleyicisinin sonucu bu olaylardan sonra atılır. Elbette davranışı değiştirmeyi seçebilirsiniz.

Bir devlet işleyicisi şöyle görünecektir:

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

Daha karmaşık

Derleme birimi çok büyük olduğunda (ne hissederseniz, 1000 satır civarında söylemeliyim), her durum işleyicisini ayrı bir dosyaya koyun. Her durum işleyici birkaç ekrandan daha uzun olduğunda, durum anahtarının bölünmesine benzer şekilde, her olayı ayrı bir işlevde ayırın. Bunu devletten ayrı olarak veya ortak bir tablo kullanarak veya çeşitli şemaları birleştirerek çeşitli şekillerde yapabilirsiniz. Bazıları burada diğerleri tarafından kapsanmıştır. Hız bir gereklilikse tablolarınızı sıralayın ve ikili aramayı kullanın.

Genel programlama

Önişlemcinin tabloları sıralamak, hatta açıklamalardan durum makineleri üretmek gibi sorunları çözmesini ve "programlar hakkında programlar yazmanıza" izin vermesini istiyorum. Bu Boost insanların C ++ şablonlarını sömürdüğü şey olduğuna inanıyorum, ancak sözdizimi şifreli buluyorum.

İki boyutlu tablolar

Geçmişte durum / olay tabloları kullandım ama en basit durumlar için onları gerekli bulamıyorum ve bir ekran tam geçmiş olsa bile anahtar deyiminin netliğini ve okunabilirliğini tercih etmeliyim. Daha karmaşık durumlarda, diğerlerinin belirttiği gibi tablolar hızla elden çıkar. Burada sunduğum deyimler, bellek tüketen bir tabloyu korumak zorunda kalmadan (program belleği olsa bile), istediğiniz zaman bir dizi olay ve durum eklemenize izin veriyor.

feragat

Özel ihtiyaçlar bu deyimleri daha az kullanışlı hale getirebilir, ancak onları çok açık ve bakımı kolay buldum.


Aslında bu ayrılmış bir kelime olmasa bile, sadece dernek için bir değişken adı veya sembolü olarak 'bu' önlemek istiyorum.
XTL

4

Son derece test edilmemiş, ancak kodlanması eğlenceli, şimdi orijinal cevabımdan daha rafine bir versiyonda; güncel sürümler mercurial.intuxication.org adresinde bulunabilir :

sm.h

#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
"Son derece denenmemiş" yorumu seviyorum. Test edilememe dereceleri olduğunu ve test etmemek için biraz çaba harcadığınızı belirtiyor gibi görünüyor :-)
paxdiablo

@Christoph bu cevaptaki bağlantı koptu. Ayrıca, bu kodu test ettiniz mi, etmediniz mi? Test edilmiş ve çalışıyorsa, cevabı kaldırmalısınız. Ayrıca, makrolar genişletildikten sonra bunun hangi kodla sonuçlandığına ilişkin bir örnek gösterebilir. Genel fikri seviyorum.
Joakim

4

Paxdiable'ın cevabını gerçekten beğendim ve koruma değişkenleri ve durum makinesine özel veriler gibi uygulamam için tüm eksik özellikleri uygulamaya karar verdim.

Uygulamamı toplulukla paylaşmak için bu siteye yükledim. ARM için IAR Gömülü Çalışma Tezgahı kullanılarak test edilmiştir.

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


Bunu 2018'de bulmak ve hala uygulanabilir olduğunu. @Paxdiablo cevabını okuyordum ve daha önce gömülü sistemlerde bu tür bir uygulamayı başarıyla kullandım. Bu çözüm paxdiablos cevap :) eksik şeyler ekler :)
Kristoffer

4

Bir başka ilginç açık kaynak aracı, statecharts.org'daki Yakindu Statechart Tools . Harel statechart'larını kullanır ve böylece hiyerarşik ve paralel durumlar sağlar ve C ve C ++ (yanı sıra Java) kodu üretir. Kütüphaneleri kullanmaz, ancak 'düz kod' yaklaşımını izler. Kod temel olarak anahtar kutusu yapılarını uygular. Kod üreteçleri de özelleştirilebilir. Ek olarak araç başka birçok özellik sunar.


3

Bu kadar geç geliyor (her zamanki gibi) ama şimdiye kadar cevapları taramak önemli bir şeyin eksik olduğunu düşünürüm;

Kendi projelerimde, geçerli her durum / olay kombinasyonu için bir işleve sahip olmamanın çok yararlı olabileceğini buldum . Durumların / olayların 2D tablosunu etkin bir şekilde kullanma fikrini seviyorum. Ancak tablo öğelerinin basit bir işlev işaretçisinden daha fazlası olmasını seviyorum. Bunun yerine tasarımımı organize etmeye çalışıyorum, böylece kalbinde bir grup basit atomik element veya eylem var. Bu şekilde, bu basit atomik elementleri durum / olay masamın her kesişiminde listeleyebilirim. Fikir, N kare (tipik olarak çok basit) fonksiyonlardan oluşan bir kitle tanımlamanıza gerek olmamasıdır . Neden bu kadar hataya eğilimli, zaman alıcı, yazması zor, okunması zor bir şey var?

Ayrıca, isteğe bağlı yeni bir durum ve tablodaki her hücre için isteğe bağlı bir işlev işaretçisi de ekliyorum. İşlev işaretçisi, sadece atomik eylemlerin bir listesini ateşlemek istemediğiniz istisnai durumlar için vardır.

Yazmanız gereken yeni bir kod olmadan, sadece tablonuzu düzenleyerek birçok farklı işlevi ifade edebildiğinizde bunu doğru yaptığınızı biliyorsunuz.


2
Belki bir örnek güzel olurdu, değil mi?
jldupont

1
Tek başına sunulabilecek gerçekçi bir örnek, şu an vermeye hazır olduğumdan daha fazla zaman gerektiren zorlu bir görevdir. Mesajımda özellikle anlaşılması zor bir şey var mı? Belki daha açık bir şekilde ifade edebilirim. Fikir, çok basit; Her olay / durum kombinasyonu için ayrı bir işlev gerektiren bir durum mekanizması tanımlamayın, bu şekilde çok fazla işlev elde edersiniz. Bunun yerine, en azından çoğu durumda, o olay / durum kombinasyonu için istediğiniz işlevselliği tanımlamanın başka bir yolunu bulun.
Bill Forster

2
Anlaşıldı: bir sözde kod örneği iyi olurdu, ancak amacınız açıktır.
jldupont

3

Alrght, bence benimki herkesinkinden biraz farklı. Diğer cevaplarda gördüğümden biraz daha kod ve veri ayrımı. Tam bir Düzenli dil (ne yazık ki düzenli ifadeler olmadan) uygulayan bunu yazmak için teoriyi gerçekten okudum. Ullman, Minsky, Chomsky. Her şeyi anladığımı söyleyemem, ama eski ustalardan mümkün olduğunca doğrudan çektim: sözleriyle.

Bir 'evet' durumuna veya 'hayır' durumuna geçişi belirleyen bir yüklem için bir işlev işaretçisi kullanıyorum. Bu, daha montaj dili gibi bir şekilde programladığınız normal bir dil için sonlu durum alıcısının oluşturulmasını kolaylaştırır. Lütfen aptal isim seçimlerimden vazgeçme. 'czek' == 'kontrol et'. 'grok' == [hacker Hacker Sözlüğü'ne bakın].

Dolayısıyla, her yineleme için czek geçerli karakterle bir yüklem işlevi çağırır. Yüklem doğru döndürürse, karakter kullanılır (işaretçi ilerler) ve bir sonraki durumu seçmek için 'y' geçişini izleriz. Yüklem yanlış döndürürse, karakter KULLANILMAZ ve 'n' geçişini izleriz. Yani her talimat iki yönlü bir dal! O zaman Mel'in Hikayesi'ni okumuş olmalıydım.

Bu kod doğrudan postscript tercümanımdan gelir ve comp.lang.c'deki arkadaşların rehberliği ile mevcut formuna dönüşmüştür. Postscript'in temelde bir sözdizimi olmadığından (yalnızca dengeli parantez gerektirir), bunun gibi bir Normal Dil Kabulleyici ayrıştırıcı olarak da işlev görür.

/* 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
Herhangi bir ayrıştırıcı veya lexer jeneratörü sizin için memnuniyetle yayacaktır. Tekinsiz. Elle kodlamak isteyip istemediğiniz tartışmalıdır. Elbette pedagojik bir değeri vardır.
Monica'yı



2

Bunu bir yerde gördüm

#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
İlginçtir, ancak bir veya iki örnek (ve belki de makro olmayan bir sonuç) verene veya bunun neden diğerinden daha pratik olabileceğine dair bir tartışma yapana kadar oy kullanmayın. Öksüz parantez ve makroların ilginç kullanımı. Bir çeşit kuyruk özyineleme optimizasyonu yapan bir dilde benzer bir şey yapılabileceğini hayal ediyorum; Eğer fonksiyon çağrısı çöp ile düz yukarı fonksiyon çağrıları kullanmak ve yığın alanı aşırı yük endişe (ki ben makrolar aslında burada üstesinden ne olduğunu düşünüyorum)
Ape-inago

2
Bu yöntemin avantajları ...? Makroları şaşırtmak gibi çeşitli dezavantajlar görüyorum ve bunun kullanımı, gotopreemtive bir çoklu görev işletim sistemine bağımlılık yaratıyor.
Craig McQueen

2

C ++ ve dolayısıyla OO kodunu kullanabileceğinizi ima edersek, 'GoF'state desenini değerlendirmenizi öneririm (GoF = Gang of Four, tasarım desenlerini dikkat çeken tasarım desenleri kitabını yazan çocuklar).

Özellikle karmaşık değildir ve yaygın olarak kullanılır ve tartışılır, bu nedenle çevrimiçi örnekleri ve açıklamaları görmek kolaydır.

Ayrıca, kodunuzu daha sonraki bir tarihte saklayan herkes tarafından tanınacaktır.

Verimlilik endişe ise, pek çok faktör performansı etkilediğinden ve her zaman basitçe OO kötü, fonksiyonel kod iyi olmadığından, OO olmayan bir yaklaşımın daha verimli olduğundan emin olmak için gerçekten karşılaştırmaya değer olacaktır. Benzer şekilde, bellek kullanımı sizin için bir kısıtlama ise, durum modelini kullanırsanız, bunun özel uygulamanız için bir sorun olup olmayacağını görmek için bazı testler veya hesaplamalar yapmaya değer.

Craig'in önerdiği gibi, 'Gof' durum modeline bazı bağlantılar:


daha çok bir yorum gibi görünüyor: Bu şekilde ele almanızı öneririm? yani "cevap" bölümüne yerleştirmeyin.
jldupont

"GoF durum kalıbı" için iyi bir URL bağlantısı sağlayabilirseniz, buna aşina olmayanlar için iyi olur.
Craig McQueen

1
@jldupont - adil yorum. Belli bir performans sorunu olmadığı sürece, GoF yaklaşımının iyi çalıştığı ve nispeten büyük bir 'kullanıcı tabanına sahip olacağı kişisel deneyime dayanarak hissettiğim gibi metni uygun bir cevap verecek şekilde değiştirdim
Mick

@Craig - bazı bağlantılar ekledi. Her ikisi de onları eklediğim anda doğru ve net görünüyordu.
Mick

2

Olaylar için mesaj kuyruklarını kullanan Linux için bir Sonlu Durum Makinesi örneği. Olaylar sıraya alınır ve sırayla işlenir. Durum, her olay için ne olduğuna bağlı olarak değişir.

Bu, aşağıdaki gibi durumlarla veri bağlantısı için bir örnektir:

  • başlatılmamış
  • Initialized
  • bağlı
  • MTU Müzakere Edildi
  • Doğrulanmış

Eklediğim küçük bir ekstra özellik, her mesaj / etkinlik için bir zaman damgasıydı. Olay işleyici, çok eski (süresi dolmuş) olayları yoksayar. Bu, gerçek dünyada beklenmedik bir durumda sıkışabileceğiniz çok şey olabilir.

Bu örnek Linux'ta çalışır, derlemek ve onunla oynamak için aşağıdaki Makefile'i kullanın.

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

Sorunuz oldukça genel,
İşte size yardımcı olabilecek iki referans makale,

  1. Gömülü Durum Makine Uygulaması

    Bu makalede, gömülü bir sistem için bir durum makinesi uygulamak için basit bir yaklaşım açıklanır. Bu makalenin amaçları doğrultusunda, bir durum makinesi az sayıda durumdan birinde olabilen bir algoritma olarak tanımlanmaktadır. Durum, girdilerin çıktılarla ve girdilerin sonraki durumlarla öngörülen ilişkisine neden olan bir durumdur.
    Bilgili bir okuyucu, bu makalede açıklanan durum makinelerinin Mealy makineleri olduğunu hızlı bir şekilde not edecektir. Mealy makinesi, çıktıların sadece durumun bir işlevi olduğu bir Moore makinesinin aksine, çıktıların hem mevcut durumun hem de girişin bir fonksiyonu olduğu bir durum makinesidir.

    • Durum Makinelerini C ve C ++ 'da Kodlama

      Bu makaledeki endişem, durum makine temelleri ve C veya C ++ 'da durum makinelerini kodlamak için bazı basit programlama yönergeleri ile ilgilidir. Umarım bu basit teknikler daha yaygın hale gelebilir, böylece siz (ve diğerleri) durum-makine yapısını doğrudan kaynak kodundan görebilirsiniz.



1

Bu çok cevaplı eski bir yazı, ama C'deki sonlu durum makinesine kendi yaklaşımımı ekleyeceğimi düşündüm. İstediğim sayıda eyalet için iskelet C kodunu üretmek için bir Python betiği yaptım. Bu komut dosyası GituHub'da şurada belgelenmiştir: FsmTemplateC

Bu örnek, okuduğum diğer yaklaşımlara dayanmaktadır. Goto veya switch deyimlerini kullanmaz, bunun yerine işaretçi matrisinde (arama tablosu) geçiş işlevlerine sahiptir. Kod, çok satırlı büyük bir başlatıcı makro ve C99 özelliklerine (belirlenmiş başlatıcılar ve bileşik değişmez değerler) dayanır, bu nedenle bu şeyleri beğenmezseniz, bu yaklaşımı beğenmeyebilirsiniz.

FsmTemplateC kullanarak iskelet C kodu üreten bir turnike örneğinin Python betiği :

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

Oluşturulan çıktı üstbilgisi typedefs içerir:

/* 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 *);
  • enum eFsmTurnstileCheck, bir geçişin engellenip engellenmediğini EFSM_TURNSTILE_TR_RETREAT, ilerlemesine izin verilip verilmediğini EFSM_TURNSTILE_TR_ADVANCEveya işlev çağrısının öncesindeEFSM_TURNSTILE_TR_CONTINUE .
  • Sıralama eFsmTurnstileState basitçe devletlerin listesidir.
  • Sıralama eFsmTurnstileInput basitçe girdi listesidir.
  • FsmTurnstileYapı geçiş kontrolü, fonksiyon, arama tablosunda, mevcut durumu, komut durumu ve makineyi çalıştıran birincil işlevi için bir takma ad ile durum makinesinin kalbidir.
  • İçindeki her işlev işaretçisi (diğer ad) FsmTurnstileyalnızca yapıdan çağrılmalı ve kalıcı bir durum, nesne yönelimli bir stil sağlamak için ilk girişini kendisine bir işaretçi olarak almalıdır.

Şimdi başlıktaki işlev bildirimleri için:

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

İşlev adları biçimdedir {prefix}_{from}_{to}; burada {from}önceki (geçerli) durum ve {to}sonraki durumdur. Geçiş tablosu belirli geçişlere izin vermiyorsa, işlev işaretçisi yerine NULL işaretçisi ayarlanacağını unutmayın. Son olarak, sihir bir makro ile olur. Burada geçiş tablosunu (durum numaralandırmalarının matrisi) oluşturuyoruz ve durum geçiş işlevleri tablosuna (fonksiyon işaretçileri matrisi) bakıyoruz:

/* 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 oluştururken, makro FSM_EXAMPLE_CREATE() kullanılması gerekir.

Şimdi, kaynak kodda, yukarıda belirtilen her durum geçiş işlevi doldurulmalıdır. FsmTurnstileFoptsYapı durum makinesinin üzerine / veri geçirmek için kullanılabilir. Her geçiş , geçişini engellemek veya komut verilen duruma geçişine izin vermek fsm->checkiçin eşit olacak şekilde ayarlanmalıdır . Çalışan bir örnek (FsmTemplateC) [ https://github.com/ChisholmKyle/FsmTemplateC] adresinde bulunabilir .EFSM_EXAMPLE_TR_RETREATEFSM_EXAMPLE_TR_ADVANCE

Kodunuzdaki çok basit gerçek kullanım:

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

Tüm bu başlık iş ve tüm bu fonksiyonları basit ve hızlı bir arayüze sahip olmak aklımda buna değer.


0

Açık kaynak kütüphanesini OpenFST kullanabilirsiniz .

OpenFst, ağırlıklı sonlu durum transdüserlerinin (FST'ler) oluşturulması, birleştirilmesi, optimize edilmesi ve aranması için bir kütüphanedir. Ağırlıklı sonlu durum transdüserleri, her geçişin bir giriş etiketi, bir çıkış etiketi ve bir ağırlığa sahip olduğu otomatalardır. Daha bilindik sonlu durum alıcısı, her geçişin giriş ve çıkış etiketinin eşit olduğu bir dönüştürücü olarak temsil edilir. Sonlu durum alıcıları, dizeleri (özellikle düzenli veya rasyonel kümeleri) temsil etmek için kullanılır; sonlu durumlu transdüserler, dize çiftleri (özellikle rasyonel transdüksiyonlar) arasındaki ikili ilişkileri temsil etmek için kullanılır. Ağırlıklar, belirli bir geçişin maliyetini temsil etmek için kullanılabilir.


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

Fonksiyonlara bir dizi sabit fonksiyon işaretçisi kullanarak güvenlik için daha da optimize edebilirsiniz
AlphaGoku

0

Şahsen kendi kendini referanslayan yapıları işaretçi dizileriyle birlikte kullanıyorum. Bir süre önce github üzerine bir eğitim yükledim, bağlantı:

https://github.com/mmelchger/polling_state_machine_c

Not: Bu iş parçacığının oldukça eski olduğunu anlıyorum, ancak umarım devlet makinesinin tasarımı hakkında girdi ve düşünceler almayı ve aynı zamanda C'de olası bir devlet makinesi tasarımına örnek vermeyi umuyorum.


0

Sen düşünebilirsiniz UML-devlet makinesini-in-c , C. I, bir "hafif" durum makinesi çerçevesi hem desteklemek için bu çerçeveyi yazdım Sonlu durum makinesi ve Hiyerarşik devlet makinesini . Durum tabloları veya basit anahtar durumlarıyla karşılaştırıldığında, bir çerçeve yaklaşımı daha ölçeklenebilirdir. Karmaşık hiyerarşik durum makinelerine basit sonlu durum makineleri için kullanılabilir.

Durum makinesi state_machine_tyapı ile temsil edilir . Yalnızca iki "Event" üyesi ve "state_t" işaretçisi içerir.

struct state_machine_t
{
   uint32_t Event;          //!< Pending Event for state machine
   const state_t* State;    //!< State of state machine.
};

state_machine_teyalet makine yapınızın ilk üyesi olmalıdır. Örneğin

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 durum için bir işleyici ve ayrıca giriş ve çıkış eylemi için isteğe bağlı işleyiciler içerir.

//! 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.
};

Çerçeve bir hiyerarşik durum makinesi için yapılandırılmışsa, state_t , üst ve alt durum için bir işaretçi içerir.

Framework dispatch_event, olayı durum makinesine göndermek ve switch_statedurum geçişini tetiklemek için bir API sağlar .

Hiyerarşik durum makinesinin nasıl uygulanacağı hakkında daha fazla ayrıntı için GitHub deposuna bakın .

kod örnekleri,

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

Makroları kullanan bir durum makinesi için, her fonksiyonun kendi durum kümesine sahip olabileceği bir yöntem: https://www.codeproject.com/Articles/37037/Macros-to-simulate-multi-tasking-blocking-code -at

"Çoklu görevi simüle" olarak adlandırılır, ancak tek kullanım bu değildir.

Bu yöntem, bıraktığı her işlevde geri çağırmak için geri çağrıları kullanır. Her işlev, her işleve özgü durumların bir listesini içerir. Durum makinelerini çalıştırmak için merkezi bir "boşta döngü" kullanılır. "Boşta çalışma döngüsü", durum makinelerinin nasıl çalıştığı hakkında hiçbir fikre sahip değildir, "ne yapacağını bilen" bireysel işlevlerdir. İşlevler için kod yazmak amacıyla, bir durum listesi oluşturulur ve "askıya almak" ve "devam ettirmek" için makrolar kullanılır. Nexus 7000 anahtarı için Alıcı-Verici Kütüphanesi'ni yazarken bu makroları Cisco'da kullandım.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.