2010-02-16 13 views
14

Qual è il modo migliore per scrivere una macchina a stati in C?
Di solito scrivo una grande istruzione switch-case in a per (;;), con callback per rientrare nella macchina a stati quando un'operazione esterna è terminata.
Conosci un modo più efficiente?Macchine di stato in C

+4

Sembra un approccio di implementazione piuttosto standard. –

+1

Ehi! Suona proprio come quello che faccio. Mondo piccolo. –

+2

Ehi! Hai rubato il mio codice e l'hai copiato! –

risposta

25

Mi piace l'approccio Quantum Leaps.

Lo stato corrente è un puntatore a una funzione che accetta un oggetto evento come argomento. Quando si verifica un evento, basta chiamare la funzione di stato con quell'evento; La funzione può quindi svolgere il proprio lavoro e passare a un altro stato semplicemente impostando lo stato su un'altra funzione.

Es .:

// State type and variable, notice that it's a function pointer. 
typedef void (*State)(int); 
State state; 

// A couple of state functions. 
void state_xyz(int event) { /*...*/ } 
void state_init(int event) { 
    if (event == E_GO_TO_xyz) { 
     // State transition done simply by changing the state to another function. 
     state = state_xyz; 
    } 
} 

// main contains the event loop here: 
int main() { 
    int e; 
    // Initial state. 
    state = state_init; 
    // Receive event, dispatch it, repeat... No 'switch'! 
    while ((e = wait_for_event()) != E_END) { 
     state(e); 
    } 
    return 0; 
} 

I quadri QL fornisce aiutanti per cose extra come le azioni di ingresso/uscita/init, macchine a stati gerarchici, ecc consiglio vivamente the book per una spiegazione più profonda e una buona attuazione del presente.

+0

ho fatto questo con l'intelligenza artificiale, modo molto bello – Craig

+0

Quanto è veloce? – Yoric

+0

Questo metodo elimina un livello di ricerca di tabelle o interruttori, poiché lo stato è un puntatore diretto a una funzione appena richiamata. Tuttavia, come al solito, puoi solo essere sicuro dell'aumento di velocità effettivo testandolo! Questo dovrebbe aiutare anche con la manutenzione, che può essere importante nel progetto abbastanza grande. – squelart

4

Questo è praticamente l'approccio standard. Se siete interessati a studiare una biblioteca ben considerato e confrontando le specifiche, dare un'occhiata a Ragel:

Ragel compila macchine a stati finiti eseguibili da linguaggi regolari. Ragel punta a C, C++, Objective-C, D, Java e Ruby. Le macchine di stato di Ragel non solo riconoscono le sequenze di byte come fanno le macchine di espressioni regolari, ma possono anche eseguire codice in punti arbitrari nel riconoscimento di un linguaggio normale. L'incorporamento del codice viene eseguito utilizzando operatori in linea che non interrompono la normale sintassi del linguaggio.

6

Il modo migliore è in gran parte soggettivo, ma un modo comune è utilizzare un approccio basato su tabelle in cui si mappano i codici di stato (enumerazione o qualche altro tipo integrale) ai puntatori di funzione. La funzione restituisce il tuo stato successivo e altri dati associati e lo fai scorrere fino a quando non viene raggiunto lo stato del terminale. Questo potrebbe in effetti essere ciò che stai descrivendo come il tuo approccio sopra.

1

Un approccio alternativo è un array 2D che descrive per ogni combinazione stato/evento le azioni da eseguire e lo stato successivo a cui andare. Questo può diventare più difficile da gestire quando è necessario passare a stati diversi a seconda delle "circostanze", ma può essere fatto per funzionare bene. Hai una funzione di riconoscimento eventi che restituisce l'evento successivo; si ha la tabella in cui ogni voce della tabella identifica la funzione da chiamare alla ricezione dell'evento e lo stato successivo a cui andare, a meno che la funzione chiamata non sostituisca quello stato.

Generare effettivamente tale codice è più fugace: dipende da come viene descritta l'FSM in primo luogo. Individuare azioni duplicate è spesso importante. Spesso, si può fare affidamento su tecniche di "sparse matrix" che non registrano esplicitamente la gestione degli errori: se la voce esiste logicamente nella matrice sparsa, si agisce su quell'evento/stato informazioni, ma se la voce non esiste si ricade su appropriato segnalazione degli errori e codice di risincronizzazione.

Un array 2D di puntatori a strutture può essere passato in una funzione FSM generica; il fatto che tu scriva un triplo puntatore è abbastanza per renderti cauto su quello che sta succedendo. (Ne scrissi uno di questi nel marzo del 1986 - non ne ho più la fonte su disco, anche se ho ancora una stampa del documento che lo descrisse.)

3

Le dichiarazioni di commutazione sono un buon modo per iniziare, ma tendono a diventare ingombranti quando l'FSM diventa più grande.

Un paio correlato (o duplicare) SO domande con grande informazioni e idee:

1

Ho usato questo schema. Is there a typical state machine implementation pattern? (controlla la risposta migliore).

Ma aggiungo anche alcune funzioni
1. Informazioni sullo stato precedente.
2. passaggio di parametri
3. Aggiunta di eventi esterni come timeout globale e "resseting SM"

ho trovato le macchine a stati poco meno criptico e gestibile.
In ogni caso, io continuo a pensare macchine a stati sono più difficili e fastidioso compito di programmazione (ho ottenuto finora)

0

Date un'occhiata qui:. http://code.google.com/p/fwprofile/

Si tratta di una versione open source (GNU GPLv3) della macchina dello Stato implementato in C. Il concetto e l'implementazione sono adatti per l'uso nelle applicazioni mission-critical . Esistono distribuzioni in applicazioni industriali .

0

Uso i puntatori di funzione e una tabella di ricerca 2D in cui utilizzo lo stato per un parametro e l'evento come l'altro.

Uso Excel (o qualsiasi strumento per fogli di calcolo) per associare una funzione a ogni combinazione stato/evento.

Quando si verifica un evento, ho que l'alto, così poi ho qualcosa che assomiglia a questo

int main(void) 
{ 
    StateList currentState = start_up; 
    EventList currentEvent; 

    uint8_t stateArray[STATE_COUNT][EVENT_COUNT]; 

    InitializeStateArray(stateArray); 
    InitializeEventQue(); 

    while(1) 
    { 
     currentEvent = GetPriorityEvent(); 
     currentState = (StateList)(*(stateArray[currentState][currentEvent]))(); 
    } 
    return 1; //should never get here 
} 

Questo metodo forza essenzialmente lo sviluppatore di prendere in considerazione tutti i possibili eventi in ogni stato, e nella mia esperienza fa debugging un po 'più facile.

Problemi correlati