在C中为FSM使用GOTO
我正在用C语言创建一个有限状态机。 我从硬件的角度(HDL语言)学习了FSM。所以我使用了一个在C中为FSM使用GOTO,c,goto,state-machine,C,Goto,State Machine,我正在用C语言创建一个有限状态机。 我从硬件的角度(HDL语言)学习了FSM。所以我使用了一个开关,每个状态有一个案例 我也喜欢在编程时应用关注点分离的概念。 我的意思是我想得到这个流程: 根据当前状态和输入标志计算下一个状态 验证下一个状态(如果用户请求不允许的转换) 在允许的情况下处理下一个状态 首先,我实现了3个功能: 静态e_internalfsm声明fsm_GetNextState(); 允许静态bool_t fsm_nextState(e_internalfsm states nex
开关
,每个状态有一个案例
我也喜欢在编程时应用关注点分离的概念。
我的意思是我想得到这个流程:
switch (FSM_currentState) {
case FSM_State1:
[...]
break;
case FSM_State2:
[...]
break;
default:
[...]
break;
}
现在它工作了,我想改进代码
我知道在这3个函数中,我将执行开关的同一个分支。
因此,我想以这种方式使用goto
s:
//
// 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;
它是否“危险”在某种程度上可能是一个意见问题。人们说要避免GOTO的通常原因是,它往往导致难以遵循的通心粉代码。这是绝对的规则吗?也许不是,但我认为可以肯定地说,这是一种趋势。其次,大多数程序员在这一点上都接受过培训,认为GOTO是不好的,因此,即使在某些情况下不是这样,您也可能会在其他人稍后进入项目时遇到某种程度的可维护性问题
在您的案例中,您的风险有多大,可能取决于您将在这些状态标签下拥有的代码块有多大,以及您是否确信它不会有多大变化。更多的代码(或可能进行大规模修订)意味着更大的风险。除了关于可读性的直接问题外,你将有更多的机会分配到变量上,这些变量会在案例之间产生干扰,或者依赖于你到达某个状态的路径。通过为变量创建局部范围,使用函数有助于实现这一点(在许多情况下)
总而言之,我建议避免GOTO。我的经验法则是使用GOTO只能向前跳转代码,而不能向后跳转。最后,这归结为仅将GOTO用于异常处理,否则在C中不存在
在您的特殊情况下,我绝对不建议使用GOTO。虽然
GOTO
在C语言中有它的优点,但应谨慎使用。您想要的不是推荐的用例
您的代码的可维护性将降低,并且更加混乱<代码>开关/大小写
实际上是某种“计算”的转到(这就是为什么有大小写标签)
你的想法基本上是错误的。对于状态机,您应该首先验证输入,然后计算下一个状态,然后计算输出。有多种方法可以做到这一点,但通常最好使用两个开关,并可能使用单个错误处理标签或错误标志:
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成为可能,因为您无论如何都必须评估输入以设置下一个状态,所以无效输入自然会落在测试的后面
另一种方法是使用输出状态
和状态
变量,而不是下一个状态
和当前状态
。在第一个开关
中设置输出状态
和状态
,第二个是开关(输出状态)…
如果单个案例
过长,则应使用函数确定下一个状态
和/或输出状态
/outputs。这在很大程度上取决于FSM(输入、输出、状态的数量、复杂性(例如,一个热的与“编码的”——如果您是HDL的家人,您会知道)
如果在循环内需要更复杂的错误处理(例如恢复),请保持循环不变并添加外循环,可能会将错误标志更改为错误代码,并在外循环中为其添加另一个开关。根据复杂性,将内循环打包为其自己的函数,等等
旁注:编译器可能会很好地优化结构化方法(没有
goto
)对于与goto相同/类似的代码,您实际上不需要使用switch case,编译器会将其优化为带有函数指针跳转表的机器代码。状态机的开关情况往往有点难以读取,尤其是更复杂的情况
意大利面goto是不可接受的,也是糟糕的编程实践:goto
有一些有效的用法,这不是其中之一
取而代之的是,考虑有一个单行状态机,它看起来像:
state = STATE_MACHINE[state]();
这是一个基于函数指针查找表的(取自电气工程网站,它几乎普遍适用)
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
}
将所有错误处理留给调用方,如上面的示例所示。GOTO没有问题。不要狂热反GOTO。虽然我不是一个反GOTO狂热者,但我真的认为在这种情况下,您应该避免这样做:您正在编写switch语句,但您不想在其中编写代码……那么为什么不使用函数呢随机转到内部