c语言状态机编程

感谢网友的分享,我才这么快掌握状态机编程,怕丢失了,把网友的帖子全部总结在这里以便查看 一 有限状态机的实现方式 有限状态机(Finite State Machine或者Finite State Automata)是软件领域中一种重要的工具,很多东西的模型实际上就是有限状态机。 FSM的实现方式: 1) switch/case或者if/else 这无意是最直观的方式,使用一堆条件判断,会编程的人都可以做到,对简单小巧的状态机来说最合适,但是毫无疑问,这样的方式比较原始,对庞大的状态机难以维护。 2) 状态表维护一个二维状态表,横坐标表示当前状态,纵坐标表示输入,表中一个元素存储下一个状态和对应的操作。这一招易于维护,但是运行时间和存储空间的代价较大。 3) 使用State Pattern 使用State Pattern使得代码的维护比switch/case方式稍好,性能上也不会有很多的影响,但是也不是100%完美。不过Robert C. Martin做了两个自动产生FSM代码的工具,for java和for C++各一个,在http://www.objectmentor.com/resources/index上有免费下载,这个工具的输入是纯文本的状态机描述,自动产生符合State Pattern的代码,这样developer的工作只需要维护状态机的文本描述,每必要冒引入bug的风险去维护code。 4) 使用宏定义描述状态机一般来说,C++编程中应该避免使用#define,但是这主要是因为如果用宏来定义函数的话,很容易产生这样那样的问题,但是巧妙的使用,还是能够产生奇妙的效果。MFC就是使用宏定义来实现大的架构的。在实现FSM的时候,可以把一些繁琐无比的if/else还有花括号的组合放在宏中,这样,在代码中可以3)中状态机描述文本一样写,通过编译器的预编译处理产生1)一样的效果,我见过产生C代码的宏,如果要产生C++代码,己软MFC可以,那么理论上也是可行的。 二 状态机的两种写法+实例 有限状态机FSM思想广泛应用于硬件控制电路设计,也是软件上常用的一种处理方法(软件上称为FMM--有限消息机)。它把复杂的控制逻辑分解成有限个稳定状态,在每个状态上判断事件,变连续处理为离散数字处理,符合计算机的工作特点。同时,因为有限状态机具有有限个状态,所以可以在实际的工程上实现。但这并不意味着其只能进行有限次的处理,相反,有限状态机是闭环系统,有限无穷,可以用有限的状态,处理无穷的事务。 有限状态机的工作原理如图1所示,发生事件(event)后,根据当前状态(cur_state),决定执行的动作(action),并设置下一个状态号(nxt_state)。 ------------- | |-------->执行动作action 发生事件event ----->| cur_state | | |-------->设置下一状态号nxt_state ------------- 当前状态 图1 有限状态机工作原理 e0/a0 --->-- | | -------->---------- e0/a0 | | S0 |----- | -<------------ | e1/a1 | | e2/a2 V ---------- ---------- | S2 |-----<-----| S1 | ---------- e2/a2 ---------- 图2 一个有限状态机实例 -------------------------------------------- 当前状态 s0 s1 s2 | 事件 -------------------------------------------- a0/s0 -- a0/s0 | e0 -------------------------------------------- a1/s1 -- -- | e1 -------------------------------------------- a2/s2 a2/s2 -- | e2 -------------------------------------------- 表1 图2状态机实例的二维表格表示(动作/下一状态) 图2为一个状态机实例的状态转移图,它的含义是: 在s0状态,如果发生e0事件,那么就执行a0动作,并保持状态不变; 如果发生e1事件,那么就执行a1动作,并将状态转移到s1态; 如果发生e2事件,那么就执行a2动作,并将状态转移到s2态; 在s1状态,如果发生e2事件,那么就执行a2动作,并将状态转移到s2态; 在s2状态,如果发生e0事件,那么就执行a0动作,并将状态转移到s0态; 有限状态机不仅能够用状态转移图表示,还可以用二维的表格代表。一般将当前状态号写在横行上,将事件写在纵列上,如表1所示。其中“--”表示空(不执行动作,也不进行状态转移),“an/sn”表示执行动作an,同时将下一状态设置为sn。表1和图2表示的含义是完全相同的。 观察表1可知,状态机可以用两种方法实现:竖着写(在状态中判断事件)和横着写(在事件中判断状态)。这两种实现在本质上是完全等效的,但在实际操作中,效果却截然不同。 ================================== 竖着写(在状态中判断事件)C代码片段 cur_state = nxt_state; switch(cur_state) { //在当前状态中判断事件 case s0: //在s0状态 if(e0_event) { //如果发生e0事件,那么就执行a0动作,并保持状态不变; 执行a0动作; //nxt_state = s0; //因为状态号是自身,所以可以删除此句,以提高运行速度。 } else if(e1_event) {//如果发生e1事件,那么就执行a1动作,并将状态转移到s1态; 执行a1动作; nxt_state = s1; } else if(e2_event) { //如果发生e2事件,那么就执行a2动作,并将状态转移到s2态; 执行a2动作; nxt_state = s2; } break; case s1: //在s1状态 if(e2_event) { //如果发生e2事件,那么就执行a2动作,并将状态转移到s2态; 执行a2动作; nxt_state = s2; } break; case s2: //在s2状态 if(e0_event) { //如果发生e0事件,那么就执行a0动作,并将状态转移到s0态; 执行a0动作; nxt_state = s0; } } ================================== 横着写(在事件中判断状态)C代码片段 ================================== //e0事件发生时,执行的函数 void e0_event_function(int * nxt_state) { int cur_state; cur_state = *nxt_state; switch(cur_state) { case s0: //观察表1,在e0事件发生时,s1处为空 case s2: 执行a0动作; *nxt_state = s0; } } //e1事件发生时,执行的函数 void e1_event_function(int * nxt_state) { int cur_state; cur_state = *nxt_state; switch(cur_state) { case s0: //观察表1,在e1事件发生时,s1和s2处为空 执行a1动作; *nxt_state = s1; } } //e2事件发生时,执行的函数 void e2_event_function(int * nxt_state) { int cur_state; cur_state = *nxt_state; switch(cur_state) { case s0: //观察表1,在e2事件发生时,s2处为空 case s1: 执行a2动作; *nxt_state = s2; } } 上面横竖两种写法的代码片段,实现的功能完全相同,但是,横着写的效果明显好于竖着写的效果。理由如下: 1、竖着写隐含了优先级排序(其实各个事件是同优先级的),排在前面的事件判断将毫无疑问地优先于排在后面的事件判断。这种if/else if写法上的限制将破坏事件间原有的关系。而横着写不存在此问题。 2、由于处在每个状态时的事件数目不一致,而且事件发生的时间是随机的,无法预先确定,导致竖着写沦落为顺序查询方式,结构上的缺陷使得大量时间被浪费。对于横着写,在某个时间点,状态是唯一确定的,在事件里查找状态只要使用switch语句,就能一步定位到相应的状态,延迟时间可以预先准确估算。而且在事件发生时,调用事件函数,在函数里查找唯一确定的状态,并根据其执行动作和状态转移的思路清晰简洁,效率高,富有美感。 总之,我个人认为,在软件里写状态机,使用横着写的方法比较妥帖。 竖着写的方法也不是完全不能使用,在一些小项目里,逻辑不太复杂,功能精简,同时为了节约内存耗费,竖着写的方法也不失为一种合适的选择。 在FPGA类硬件设计中,以状态为中心实现控制电路状态机(竖着写)似乎是唯一的选择,因为硬件不太可能靠事件驱动(横着写)。不过,在FPGA里有一个全局时钟,在每次上升沿时进行状态切换,使得竖着写的效率并不低。虽然在硬件里竖着写也要使用IF/ELSIF这类查询语句(用VHDL开发),但他们映射到硬件上是组合逻辑,查询只会引起门级延迟(ns量级),而且硬件是真正并行工作的,这样竖着写在硬件里就没有负面影响。因此,在硬件设计里,使用竖着写的方式成为必然的选择。这也是为什么很多搞硬件的工程师在设计软件状态机时下意识地只使用竖着写方式的原因,盖思维定势使然也。 TCP和PPP框架协议里都使用了有限状态机,这类软件状态机最好使用横着写的方式实现。以某TCP协议为例,见图3,有三种类型的事件:上层下达的命令事件;下层到达的标志和数据的收包事件;超时定时器超时事件。 上层命令(open,close)事件 ----------------------------------- -------------------- | TCP | <----------超时事件timeout -------------------- ----------------------------------- RST/SYN/FIN/ACK/DATA等收包事件 图3 三大类TCP状态机事件 由图3可知,此TCP协议栈采用横着写方式实现,有3种事件处理函数,上层命令处理函数(如tcp_close);超时事件处理函数(tmr_slow);下层收包事件处理函数(tcp_process)。值得一提的是,在收包事件函数里,在各个状态里判断RST/SYN/FIN/ACK/DATA等标志(这些标志类似于事件),看起来象竖着写方式,其实,如果把包头和数据看成一个整体,那么,RST/SYN/FIN/ACK/DATA等标志就不必被看成独立的事件,而是属于同一个收包事件里的细节,这样,就不会认为在状态里查找事件,而是总体上看,是在收包事件里查找状态(横着写)。 在PPP里更是到处都能见到横着写的现象,有时间的话再细说。我个人感觉在实现PPP框架协议前必须了解横竖两种写法,而且只有使用横着写的方式才能比较完美地实现PPP。 用C语言实现有限状态机--读《C专家编程》 有限状态机(finite state machine)是一个数学概念,如果把它运用于程序中,可以发挥很大的作用。它是一种协议,用于有限数量的子程序("状态")的发展变化。每个子程序进行一些处理并选择下一种状态(通常取决于下一段输入)。 有限状态机(FSM)可以用作程序的控制结构。FSM对于那些基于输入的在几个不同的可选动作中进行循环的程序尤其合适。投币售货机就是FSM的一个好例子。另外一个你可以想到的复杂的例子就是你正在用的东西,想到了吗?没错,就是操作系统。在投币售货机的例子中,输入是硬币,输出是待售商品,售货机有"接受硬币","选择商品","发送商品"和"找零钱"等几种状态。 它的基本思路是用一张表保存所有可能的状态,并列出进入每个状态时可能执行的所有动作,其中最后一个动作就是计算(通常在当前状态和下一次输入字符的基础上,另外再经过一次表查询)下一个应该进入的状态。你从一个"初始状态"开始。在这一过程中,翻译表可能告诉你进入了一个错误状态,直到到达结束状态。 在C语言中,有好几种方法可以用来表达FSM,但它们绝大多数都是基于函数指针数组。一个函数指针数组可以像下面这样声明: void (*state[MAX_STATES]) (); 如果知道了函数名,就可以像下面这样对数组进行初始化。 extern int a(),b(),c(),d(); int (*state[]) ()={a,b,c,c}; 可以通过数组中的指针来调用函数: (*state[i]) (); 所有函数必须接受同样的参数,并返回同种类型的返回值(除非你把数组元素做成一个联合)。函数指针是很有趣的。注意,我们可以去掉指针形式,把上面的调用写成: state[i] (); 甚至 (******state[i]) (); 这是一个在ANSI C中流行的不良方法:调用函数和通过指针调用函数(或任意层次的指针间接引用)可以使用同一种语法。 如果你想干得漂亮一点,可以让状态函数返回一个指向通用后续函数的指针,并把它转换为适当的类型。这样,就不需要全局变量了。如果你不想搞得太花哨,可以使用一个switch语句作为一种简朴的状态机,方法是赋值给控制变量并把switch语句放在循环内部。关于FSM还有最后一点需要说明:如果你的状态函数看上去需要多个不同的参数,可以考虑使用一个参数计数器和一个字符串指针数组,就像main函数的参数一样。我们熟悉的int argc,char *argv[]机制是非常普遍的,可以成功地应用在你所定义的函数中。 实例:密码锁:以思维密码校验作为状态机的例子,连续输入2479就可以通过密码测试。 代码一: #include #include #include typedef enum { STATE0 = 0, STATE1, STATE2, STATE3, STATE4, }STATE; typedef enum { INPUT1 = '2', INPUT2 = '4', INPUT3 = '7', INPUT4 = '9', }INPUT; int main() { char ch; STATE current_state = STATE0; while(1) { printf("please input number to decode:"); while((ch = getchar())!='/n') { if((ch<'0')||(ch>'9')) { printf("not number, please input again!/n"); break; } switch(current_state) { case STATE0: if(ch == '2') current_state = STATE1; break; case STATE1: if(ch == '4') current_state = STATE2; break; case STATE2: if(ch == '7') current_state = STATE3; break; case STATE3: if(ch == '9') current_state = STATE4; break; default: current_state = STATE0; break; } } if(current_state == STATE4) { printf("corrent, lock is open!/n"); current_state = STATE0; } else { printf("wrong, unlocked!/n"); current_state = STATE0; } } return 0; } 代码二: //FSMstate.h typedef enum{ STATE0 = 0, STATE1, STATE2, STATE3, STATE4, }STATE; typedef enum{ INPUT1 = '2', INPUT2 = '4', INPUT3 = '7', INPUT4 = '9', }INPUT; typedef struct { STATE cur_state; INPUT input; STATE next_state; }STATE_TRANS; //FSMstate.c #include #include"FSMstate.h" /*typedef enum { STATE0 = 0, STATE1, STATE2, STATE3, STATE4, }STATE; typedef enum { INPUT1 = '2', INPUT2 = '4', INPUT3 = '7', INPUT4 = '9', }INPUT; typedef struct { STATE cur_state; INPUT input; STATE next_state; }STATE_TRANS; */ STATE_TRANS state_trans_array[]= { {STATE0,INPUT1,STATE1}, {STATE1,INPUT2,STATE2}, {STATE2,INPUT3,STATE3}, {STATE3,INPUT4,STATE4}, }; #define STATE_TRANS_CNT (sizeof(state_trans_array)/sizeof(state_trans_array[0])) int main() { int i; char ch; STATE state_machine = STATE0; while(ch!= 'e') { ch = getchar(); if((ch>= '0')&&(ch<='9')) { for(i = 0;i #include #include //Finite state machine declaration //state declaration #define IDLE 0 //idle state in rx mode #define M_BROADCAST 1 //broadcast state in tx mode,broadcast to be a master point #define M_WAIT_BROADCAST_ACK 2 //wait for broadcast ack state in rx mode,wait for the point ack in a specific time window #define M_WAIT_COMMAND 3 //wait for command state,wait for PC command via UART #define M_BROADCAST_CANCEL 4 //broadcast cancel state,broadcast to cancel master point #define S_BROADCAST_ACK 5 //slave mode,send back self physical address #define S_WAIT_COMMAND 6 //slave mode, wait for command from the master point //state transition trig //used in master mode int isReqBeMaster = 0;//Is PC request the point to be master? int isTimeout = 0;//Is time out? int isReqCancelMaster = 0;//Is request to cancel master? //used in slave mode int isRxBroadcast = 0;//Is there a point broadcast to be master? int isRxBroadcastCancel = 0;//Is receive broadcast cancel master? typedef struct fsmtag { int state; //state int timeouttime; //time out time in milliseconds }fsm; //function prototype int main() { fsm f; f.state = IDLE; f.timeouttime = 0; while(1) { switch(f.state) { case IDLE: puts("IDLE/nWait for isReqBeMaster(1/0) isRxBroadcast(1/0):"); scanf("%d %d",&isReqBeMaster,&isRxBroadcast); if(isReqBeMaster) { f.state = M_BROADCAST; break; } else if(isRxBroadcast) { f.state = S_BROADCAST_ACK; break; } else break; case M_BROADCAST: puts("M_BROADCAST/nBroadcasting.../n"); f.state = M_WAIT_BROADCAST_ACK; case M_WAIT_BROADCAST_ACK: puts("M_WAIT_BROADCAST_ACK/nWaiting for isTimeout(1/0):"); scanf("%d",&isTimeout); if(isTimeout) { f.state = M_WAIT_COMMAND; break; } else break; case M_WAIT_COMMAND: puts("M_WAIT_COMMAND/nWaiting for isReqCancelMaster(1/0):"); scanf("%d",&isReqCancelMaster); if(isReqCancelMaster) { f.state = IDLE; break; } else break; //Slave mode routine case S_BROADCAST_ACK: puts("S_BROADCAST_ACK/nAcking.../n"); f.state = S_WAIT_COMMAND; break; case S_WAIT_COMMAND: puts("S_WAIT_COMMAND/nWaiting for isRxBroadcastCancel(1/0):"); scanf("%d",&isRxBroadcastCancel); if(isRxBroadcastCancel) { f.state = IDLE; break; } else break; default: puts("default"); printf("%d/n",rand()); f.state = IDLE; } } return 0; } 本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/ctwei85/archive/2010/08/23/5832092.aspx

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值