Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/c/64.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
在C中为FSM使用GOTO_C_Goto_State Machine - Fatal编程技术网

在C中为FSM使用GOTO

在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

我正在用C语言创建一个有限状态机。 我从硬件的角度(HDL语言)学习了FSM。所以我使用了一个
开关
,每个状态有一个
案例

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

  • 根据当前状态和输入标志计算下一个状态
  • 验证下一个状态(如果用户请求不允许的转换)
  • 在允许的情况下处理下一个状态
  • 首先,我实现了3个功能: 静态e_internalfsm声明fsm_GetNextState(); 允许静态bool_t fsm_nextState(e_internalfsm states nextState); 静态无效fsm_ExecuteNewState(e_InternalFSM状态)

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

    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语句,但您不想在其中编写代码……那么为什么不使用函数呢随机转到内部