我在C中创建了一个有限状态机,我从硬件的角度(HDL语言)学习了FSM。所以我使用的是一个switch,每个州只有一个case。
我也喜欢在编程时应用分离关注的概念。我的意思是,我想得到这个流量:
首先,我实现了3个功能:静态e_InternalFsmStates fsm_GetNextState();静态bool_t fsm_NextStateIsAllowed(e_InternalFsmStates nextState);静态空洞fsm_ExecuteNewState(e_InternalFsmStates);
目前,它们都包含一个大的开关箱,这是相同的:
switch (FSM_currentState) {
case FSM_State1:
[...]
break;
case FSM_State2:
[...]
break;
default:
[...]
break;
}现在它已经工作了,我想改进代码。
我知道,在这3个函数中,我将执行交换机的同一个分支。因此,我想以这种方式使用gotos:
//
// Compute next state
//
switch (FSM_currentState) {
case FSM_State1:
next_state = THE_NEXT_STATE
goto VALIDATE_FSM_State1_NEXT_STATE;
case FSM_State2:
next_state = THE_NEXT_STATE
goto VALIDATE_FSM_State2_NEXT_STATE;
[...]
default:
[...]
goto ERROR;
}
//
// Validate next state
//
VALIDATE_FSM_State1_NEXT_STATE:
// Some code to Set stateIsValid to TRUE/FALSE;
if (stateIsValid == TRUE)
goto EXECUTE_STATE1;
else
goto ERROR;
VALIDATE_FSM_State2_NEXT_STATE:
// Some code to Set stateIsValid to TRUE/FALSE;
if (stateIsValid == TRUE)
goto EXECUTE_STATE2;
else
goto ERROR;
//
// Execute next state
//
EXECUTE_STATE1:
// Do what I need for state1
goto END;
EXECUTE_STATE2:
// Do what I need for state2
goto END;
//
// Error
//
ERROR:
// Error handling
goto END;
END:
return; // End of function当然,我可以在一个开关的情况下完成三个部分(计算、验证和处理下一个状态)。但是对于代码可读性和代码评审,我觉得将它们分开会更容易。
最后,我的问题是,用这种方式使用GOTOs是危险的吗?在使用FSM时,你会有什么建议吗?
谢谢您的评论!
在阅读了下面的答案和评论之后,下面是我要尝试的:
e_FSM_InternalStates nextState = FSM_currentState;
bool_t isValidNextState;
//
// Compute and validate next state
//
switch (FSM_currentState) {
case FSM_State1:
if (FSM_inputFlags.flag1 == TRUE)
{
nextState = FSM_State2;
}
[...]
isValidNextState = fsm_validateState1Transition(nextState);
case FSM_State2:
if (FSM_inputFlags.flag2 == TRUE)
{
nextState = FSM_State3;
}
[...]
isValidNextState = fsm_validateState2Transition(nextState);
}
//
// If nextState is invalid go to Error
//
if (isValidNextState == FALSE) {
nextState = FSM_StateError;
}
//
// Execute next state
//
switch (nextState) {
case FSM_State1:
// Execute State1
[...]
case FSM_State2:
// Execute State1
[...]
case FSM_StateError:
// Execute Error
[...]
}
FSM_currentState = nextState;发布于 2015-08-24 14:05:39
虽然goto在C中有它的优点,但它的使用应该非常谨慎。你想要的是没有推荐的用例。
您的代码将更不容易维护,更容易混淆。switch/case实际上是某种“经过计算的”goto (这就是为什么有case 标签)。
你的想法是错误的。对于状态机,您应该首先验证输入,然后计算下一个状态,然后是输出。这样做有多种方法,但使用两个开关(可能是一个错误处理标签或错误标志)往往是个好主意:
bool error_flag = false;
while ( run_fsm ) {
switch ( current_state ) {
case STATE1:
if ( input1 == 1 )
next_state = STATE2;
...
else
goto error_handling; // use goto
error_flag = true; // or the error-flag (often better)
break;
...
}
if ( error_flag )
break;
switch ( next_state ) {
case STATE1:
output3 = 2;
// if outputs depend on inputs, similar to the upper `switch`
break;
...
}
current_state = next_state;
}
error_handling:
...这样,您就可以立即转换和验证输入。这使得senase,因为您必须评估输入,以设置下一个状态,所以无效的输入只是自然下降的测试。
另一种选择是有一个output_state和state变量,而不是next_state和current_state。在第一个switch中,设置了output_state和state,第二个是switch ( output_state ) ...。
如果单个case的长度太长,则应该使用函数来确定next_state和/或output_state/outputs。这在很大程度上取决于FSM (输入、输出、状态、复杂性的数量(例如,一个热点与“编码”)--如果您是HDL家族,您就会知道)。
如果您需要在循环中进行更复杂的错误处理(例如恢复),请保持循环的原样并添加一个外部循环,可能会将错误标志更改为错误代码,并在外部循环中为其添加另一个开关。根据复杂性,将内环封装到自己的功能中,等等。
Sidenote:编译器很可能很好地将结构化方法(没有goto)优化为与goto相同/类似的代码。
发布于 2015-08-24 13:09:11
这是否“危险”可能在某种程度上是一个意见问题。人们说避免GOTO的通常原因是它会导致很难理解的意大利面代码。这是绝对规则吗?也许不是,但我认为这绝对是公平的,这是一个趋势。其次,目前大多数程序员都被训练成相信GOTO是坏的,所以,即使在某些情况下,您可能会在其他人进入项目时遇到某种程度的可维护性问题。
在您的情况下,您有多大的风险,可能取决于您将在这些州标签下的代码块的多大,以及您如何确定它不会有太大的变化。更多的代码(或潜在的大规模修订),意味着更多的风险。除了直截了当的可读性问题外,你将有更多的机会分配给变量,干扰情况,或者依赖于达到某种状态的路径。通过为变量创建本地范围,使用函数(在许多情况下)有助于实现这一点。
总之,我建议你避开后藤。
发布于 2015-08-24 14:20:50
您实际上并不需要使用开关大小写,它实际上会被编译器优化成带有函数指针跳转表的机器代码。状态机的开关用例往往比较难读,特别是比较复杂的。
意大利面-gotos是不可接受的和糟糕的编程实践:有一些有效的使用goto,这不是其中之一。
相反,考虑有一个单行状态机,它看起来像:
state = STATE_MACHINE[state]();下面是基于函数指针查找表的an answer of mine (取自电气工程站点,它几乎是通用的)。
typedef enum
{
STATE_S1,
STATE_S2,
...
STATE_N // the number of states in this state machine
} state_t;
typedef state_t (*state_func_t)(void);
state_t do_state_s1 (void);
state_t do_state_s2 (void);
static const state_func_t STATE_MACHINE [STATE_N] =
{
&do_state_s1,
&do_state_s2,
...
};
void main()
{
state_t state = STATE_S1;
while (1)
{
state = STATE_MACHINE[state]();
}
}
state_t do_state_s1 (void)
{
state_t result = STATE_S1;
// stuff
if (...)
result = STATE_S2;
return result;
}
state_t do_state_s2 (void)
{
state_t result = STATE_S2;
// other stuff
if (...)
result = STATE_S1;
return result;
}您也可以轻松地修改函数签名以包含错误代码,例如:
typedef err_t (*state_func_t)(state_t*); 具有如下功能
err_t do_state_s1 (state_t* state); 在这种情况下,调用者将以以下形式结束:
error = STATE_MACHINE[state](&state);
if(error != NO_ERROR)
{
// handle errors here
} 将所有错误处理留给调用方,如上面的示例所示。
https://stackoverflow.com/questions/32182876
复制相似问题