Mastering Finite State Machines in C: From Simple If/Else to Function Pointers
This article explains what a finite state machine (FSM) is, where it is commonly used, and walks through three C implementations—plain if/else, switch‑case, and a function‑pointer table—highlighting their advantages, drawbacks, and providing complete code examples.
Finite State Machine (FSM) is a mathematical model describing a limited set of states and the transitions between them, widely used in parsers, protocol handlers, game AI, and server logic to keep code clear and maintainable.
Typical Application Scenarios
FSMs appear in tokenizers for programming or natural languages, bottom‑up parsers, communication protocol implementations, and game AI, among other domains.
Implementation 1: Using if/else if Statements
The most straightforward method employs a series of if/else if checks on the current state and executes the corresponding action, then manually updates the state variable.
enum {
GET_UP,
GO_TO_SCHOOL,
HAVE_LUNCH,
GO_HOME,
DO_HOMEWORK,
SLEEP,
};
int main() {
int state = GET_UP;
while (1) {
if (state == GET_UP) {
GetUp(); // perform action
state = GO_TO_SCHOOL; // transition
} else if (state == GO_TO_SCHOOL) {
Go2School();
state = HAVE_LUNCH;
} else if (state == HAVE_LUNCH) {
HaveLunch();
state = GO_HOME;
} else if (state == SLEEP) {
Go2Bed();
state = GET_UP;
}
}
return 0;
}While easy to understand, this approach quickly becomes unwieldy as the number of states grows, leading to code bloat and reduced readability.
Implementation 2: Using switch Statements
A switch on the current state makes the structure clearer, but it still suffers from the same scalability issues when many states are involved.
int state = GET_UP;
while (1) {
switch (state) {
case GET_UP:
GetUp();
state = GO_TO_SCHOOL;
break;
case GO_TO_SCHOOL:
Go2School();
state = HAVE_LUNCH;
break;
case HAVE_LUNCH:
HaveLunch();
state = GO_HOME;
break;
case SLEEP:
Go2Bed();
state = GET_UP;
break;
default:
break;
}
}Implementation 3: Using Function Pointers and a State Table
This method builds a table that maps (event, current state) tuples to an action function and the next state, enabling compact and extensible FSMs suitable for large projects.
/* Define states */
enum {
GET_UP,
GO_TO_SCHOOL,
HAVE_LUNCH,
DO_HOMEWORK,
SLEEP,
};
/* Define events */
enum {
EVENT1 = 1,
EVENT2,
EVENT3,
};
/* Table entry structure */
typedef struct {
int event; // incoming event
int CurState; // current state
void (*eventActFun)(); // action function
int NextState; // state after action
} FsmTable_t;
/* Example action functions */
void GetUp() { /* ... */ }
void Go2School() { /* ... */ }
void HaveLunch() { /* ... */ }
void DoHomework() { /* ... */ }
void Go2Bed() { /* ... */ }
/* State table for "XiaoMing" example */
FsmTable_t XiaoMingTable[] = {
{ EVENT1, SLEEP, GetUp, GET_UP },
{ EVENT2, GET_UP, Go2School, GO_TO_SCHOOL },
{ EVENT3, GO_TO_SCHOOL, HaveLunch, HAVE_LUNCH },
{ EVENT1, HAVE_LUNCH, DoHomework, DO_HOMEWORK },
{ EVENT2, DO_HOMEWORK, Go2Bed, SLEEP },
// add more rows as needed
};
/* FSM core structure */
typedef struct {
int curState;
FsmTable_t *FsmTable;
} FSM_t;
void FSM_Regist(FSM_t *pFsm, FsmTable_t *pTable) {
pFsm->FsmTable = pTable;
}
void FSM_StateTransfer(FSM_t *pFsm, int state) {
pFsm->curState = state;
}
void FSM_EventHandle(FSM_t *pFsm, int event) {
FsmTable_t *pActTable = pFsm->FsmTable;
void (*eventActFun)() = NULL;
int NextState = 0;
int CurState = pFsm->curState;
int flag = 0;
for (int i = 0; i < sizeof(XiaoMingTable)/sizeof(FsmTable_t); ++i) {
if (event == pActTable[i].event && CurState == pActTable[i].CurState) {
flag = 1;
eventActFun = pActTable[i].eventActFun;
NextState = pActTable[i].NextState;
break;
}
}
if (flag && eventActFun) {
eventActFun(); // execute action
FSM_StateTransfer(pFsm, NextState); // move to next state
}
}
int main() {
FSM_t fsm = { .curState = SLEEP };
FSM_Regist(&fsm, XiaoMingTable);
int event = EVENT1;
while (1) {
printf("event %d is coming...
", event);
FSM_EventHandle(&fsm, event);
printf("fsm current state %d
", fsm.curState);
sleep(1); // pause for observation
// rotate events for demonstration
event = (event % 3) + 1;
}
return 0;
}The table‑driven approach keeps the core FSM engine tiny; adding new behavior only requires inserting another row into the table, making the design highly maintainable for large‑scale state machines.
Comparison and Takeaways
If/else : simplest but quickly becomes messy with many states.
Switch : clearer than chained if s but still suffers from code duplication for large systems.
Function‑pointer table : most scalable; the FSM logic stays constant while the table grows, enabling easy extensions and better separation of concerns.
All three implementations are demonstrated with a "XiaoMing's day" scenario, showing how the FSM transitions from waking up to going to school, having lunch, doing homework, and sleeping, then repeats.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
ITPUB
Official ITPUB account sharing technical insights, community news, and exciting events.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
