在C中使用GOTO进行FSM [英] Using GOTO for a FSM in C

查看:83
本文介绍了在C中使用GOTO进行FSM的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在用C创建一个有限状态机. 我从硬件角度(HDL语言)学习了FSM.所以我用的是switch,每个状态一个case.

I am creating a finite state machine in C. I learned FSM from the hardware point of view (HDL language). So I'm used a switch with one case per state.

我也喜欢在编程时应用关注分离"的概念. 我的意思是我想得到这个流程:

I also like to apply the Separation of Concerns concept when programing. I mean I'd like to get this flow:

  1. 根据当前状态和输入标志计算下一个状态
  2. 验证下一个状态(如果用户请求不允许的转换)
  3. 在允许的情况下处理下一个状态

首先,我实现了3个功能: 静态e_InternalFsmStates fsm_GetNextState(); 静态bool_t fsm_NextStateIsAllowed(e_InternalFsmStates nextState); 静态void fsm_ExecuteNewState(e_InternalFsmStates);

As a start I implemented 3 functions: static e_InternalFsmStates fsm_GetNextState(); static bool_t fsm_NextStateIsAllowed(e_InternalFsmStates nextState); static void fsm_ExecuteNewState(e_InternalFsmStates);

目前,它们都包含一个大的开关盒,它们是相同的:

At the moment they all contain a big switch-case which is the same:

switch (FSM_currentState) {
case FSM_State1:
    [...]
    break;
case FSM_State2:
    [...]
    break;
default:
    [...]
    break;
}

现在它可以工作了,我想改进代码.

Now that it works, I'd like to improve the code.

我知道在这3个函数中,我将执行开关的同一分支. 因此,我正在考虑以这种方式使用goto:

I know that in the 3 functions I'll execute the same branch of the switch. So I am thinking to use gotos in this way:

//
// 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

当然,我可以在一个开关案例中完成这三个部分(计算,验证和处理下一个状态).但是对于代码的可读性和代码审查,我觉得将它们分开会更容易.

Of course, I could do the 3 parts (calculate, validate and process the next state) in a single switch case. But for code readability and code reviews, I feel like it will be easier to separate them.

最后我的问题是,以这种方式使用GOTO危险吗? 这样使用FSM时,您有什么建议吗?

Finally my question, is it dangerous to use GOTOs in this way? Would you have any advice when using FSM like that?

谢谢您的评论!

阅读下面的答案和评论后,我将尝试以下方法:

After reading the answers and comments below, here is what I am going to try:

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在C语言中有其优点,但应谨慎使用,并且要格外小心.您打算使用的不是推荐的用例.

While goto has its benefits in C, it should be used sparesly and with extreme caution. What you intend is no recommendable use-case.

您的代码将难以维护且更加混乱. switch/case实际上是某种计算的" goto(这就是为什么存在 case labels )的原因.

Your code will be less maintainable and more confusing. switch/case is actually some kind of "calculated" goto (thats's why there are case labels).

您基本上是在思考错误的方式.对于状态机,您应该首先验证输入,然后计算下一个状态,然后计算输出.这样做的方法多种多样,但通常最好使用两个开关,并且-可能-使用单个错误处理标签或错误标志:

You are basicaly thinking the wrong way. For a state-machine, you should first verify input, then calculate the next state, then the output. There are various ways to do so, but it is often a good idea to use two switches and - possibly - a single error-handling label or a error-flag:

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:
    ...

这样,您可以立即转换和验证输入.这很容易引起人们的注意,因为您必须评估输入以设置下一个状态,因此无效的输入自然会落入测试范围.

This way you are transitioning and verifying input at once. This makes senase, as you have to evaluate the inputs anyway to set the next state, so invalid input just falls of the tests naturally.

另一种选择是使用output_statestate变量而不是next_statecurrent_state.在第一个switch中设置了output_statestate,第二个是switch ( output_state ) ....

An alternative is to have an output_state and state variable instead of next_state and current_state. In the first switch you set output_state and state, the second is switch ( output_state ) ....

如果单个case太长,则应使用函数确定next_state和/或output_state/输出.它在很大程度上取决于FSM(输入,输出,状态,复杂性的数量(例如,一键式与编码式"-如果您是HDL家庭成员,您会知道).

If the single cases become too long, you should use functions to determine the next_state and/or output_state/outputs. It depends very much on the FSM (number of inputs, outputs, states, complexity (e.g. one-hot vs. "encoded" - if you are family with HDL, you will know).

如果您需要在循环内部进行更复杂的错误处理(例如,恢复),请保持循环不变并添加一个外部循环,可以将错误标志更改为错误代码,并在其中添加另一个开关.外循环.根据复杂性,将内部循环打包到其自己的函数中,等等.

If you need more complex error-handling (e.g. recover) inside the loop, leave the loop as-is and add an outer loop, possibly change the error-flag to an error-code and add another switch for it in the outer loop. Depending on complexity, pack the inner loop into its own function, etc.

旁注:编译器可能很好地将结构化方法(没有goto)优化为与goto

Sidenote: The compiler might very well optimize the structured approach (without goto) to the same/similar code as with the goto

这篇关于在C中使用GOTO进行FSM的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆