用还是不用goto?

发布于 2024-09-07 12:03:19 字数 234 浏览 17 评论 0原文

这个问题可能听起来很陈词滥调,但我现在就处于这种情况。

我正在尝试实现一个有限状态自动机来解析 C 中的某个字符串。当我开始编写代码时,我意识到如果我使用标签来标记不同的状态并使用 goto 从一个状态跳转到另一个状态,代码可能会更具可读性。另一种视情况而定。

在这种情况下,使用标准中断和标志变量非常麻烦,并且很难跟踪状态。

什么方法更好?最重要的是,我担心这会给我的老板留下不好的印象,因为我正在实习。

This question may sound cliched, but I am in a situation here.

I am trying to implement a finite state automaton to parse a certain string in C. As I started writing the code, I realised the code may be more readable if I used labels to mark the different states and use goto to jump from one state to another as the case comes.

Using the standard breaks and flag variables is quite cumbersome in this case and hard to keep track of the state.

What approach is better? More than anything else I am worried it may leave a bad impression on my boss, as I am on an internship.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(9

渡你暖光 2024-09-14 12:03:19

goto 本身并没有什么问题。它们经常被认为是“禁忌”的原因是一些程序员(通常来自汇编世界)使用它们来创建几乎无法理解的“意大利面条”代码。如果您可以使用 goto 语句,同时保持代码干净、可读且无错误,那么您的能力就会更大。

使用 goto 语句和每个状态的一段代码绝对是编写状态机的一种方法。另一种方法是创建一个保存当前状态的变量,并使用 switch 语句(或类似语句)根据状态变量的值选择要执行的代码块。请参阅 Aidan Cully 的答案,了解使用第二种方法的好模板。

实际上,这两种方法非常相似。如果您使用状态变量方法编写状态机并编译它,则生成的程序集可能非常类似于使用 goto 方法编写的代码(取决于编译器的优化级别)。 goto 方法可以看作是从状态变量方法中优化掉额外的变量和循环。您使用哪种方法是个人选择的问题,只要您正在生成工作的、可读的代码,我希望您的老板不会因为使用一种方法而认为您使用另一种方法有什么不同。

如果您要将此代码添加到已包含状态机的现有代码库中,我建议您遵循已在使用的约定。

There is nothing inherently wrong with goto. The reason they are often considered "taboo" is because of the way that some programmers (often coming from the assembly world) use them to create "spaghetti" code that is nearly impossible to understand. If you can use goto statements while keeping your code clean, readable, and bug-free, then more power to you.

Using goto statements and a section of code for each state is definitely one way of writing a state machine. The other method is to create a variable that will hold the current state and to use a switch statement (or similar) to select which code block to execute based on the value of the state variable. See Aidan Cully's answer for a good template using this second method.

In reality, the two methods are very similar. If you write a state machine using the state variable method and compile it, the generated assembly may very well resemble code written using the goto method (depending on your compiler's level of optimization). The goto method can be seen as optimizing out the extra variable and loop from the state variable method. Which method you use is a matter of personal choice, and as long as you are producing working, readable code I would hope that your boss wouldn't think any different of you for using one method over the other.

If you are adding this code to an existing code base which already contains state machines, I would recommend that you follow whichever convention is already in use.

疯了 2024-09-14 12:03:19

使用 goto 来实现状态机通常很有意义。如果您确实担心使用 goto,合理的替代方案通常是修改一个 state 变量,以及基于该变量的 switch 语句:

typedef enum {s0,s1,s2,s3,s4,...,sn,sexit} state;

state nextstate;
int done = 0;

nextstate = s0;  /* set up to start with the first state */
while(!done)
   switch(nextstate)
      {
         case s0:
            nextstate = do_state_0();
            break;
         case s1:
            nextstate = do_state_1();
            break;
         case s2:
            nextstate = do_state_2();
            break;
         case s3:
             .
             .
             .
             .
         case sn:
            nextstate = do_state_n();
            break;
         case sexit:
            done = TRUE;
            break;
         default:
            /*  some sort of unknown state */
            break;
      }

Using a goto for implementing a state machine often makes good sense. If you're really concerned about using a goto, a reasonable alternative is often to have a state variable that you modify, and a switch statement based on that:

typedef enum {s0,s1,s2,s3,s4,...,sn,sexit} state;

state nextstate;
int done = 0;

nextstate = s0;  /* set up to start with the first state */
while(!done)
   switch(nextstate)
      {
         case s0:
            nextstate = do_state_0();
            break;
         case s1:
            nextstate = do_state_1();
            break;
         case s2:
            nextstate = do_state_2();
            break;
         case s3:
             .
             .
             .
             .
         case sn:
            nextstate = do_state_n();
            break;
         case sexit:
            done = TRUE;
            break;
         default:
            /*  some sort of unknown state */
            break;
      }
极致的悲 2024-09-14 12:03:19

如果我想给我的用户留下好印象,我会使用 FSM 生成器,例如 Ragel老板。

这种方法的主要好处是您能够在更高的抽象级别描述状态机,并且不需要关心是否使用 goto 还是 switch。更不用说在 Ragel 的特殊情况下,您可以自动获取 FSM 的漂亮图表,在任何点插入操作,自动最小化状态数量以及各种其他好处。我是否提到过生成的 FSM 也非常快?

缺点是它们更难调试(自动可视化在这里很有帮助)并且您需要学习一种新工具(如果您有一台简单的机器并且您不太可能经常编写机器,那么这可能不值得。 )

I'd use a FSM generator, like Ragel, if I wanted to leave a good impression on my boss.

The main benefit of this approach is that you are able to describe your state machine at a higher level of abstraction and don't need to concern yourself of whether to use goto or a switch. Not to mention in the particular case of Ragel that you can automatically get pretty diagrams of your FSM, insert actions at any point, automatically minimize the amount of states and various other benefits. Did I mention that the generated FSMs are also very fast?

The drawbacks are that they're harder to debug (automatic visualization helps a lot here) and that you need to learn a new tool (which is probably not worth it if you have a simple machine and you are not likely to write machines frequently.)

毁梦 2024-09-14 12:03:19

我将使用一个变量来跟踪您所处的状态,并使用一个开关来处理它们:

fsm_ctx_t ctx = ...;
state_t state = INITIAL_STATE;

while (state != DONE)
{
    switch (state)
    {
    case INITIAL_STATE:
    case SOME_STATE:
        state = handle_some_state(ctx)
        break;

    case OTHER_STATE:
        state = handle_other_state(ctx);
        break;
    }
}

I would use a variable that tracks what state you are in and a switch to handle them:

fsm_ctx_t ctx = ...;
state_t state = INITIAL_STATE;

while (state != DONE)
{
    switch (state)
    {
    case INITIAL_STATE:
    case SOME_STATE:
        state = handle_some_state(ctx)
        break;

    case OTHER_STATE:
        state = handle_other_state(ctx);
        break;
    }
}
夜唯美灬不弃 2024-09-14 12:03:19

Goto 并不是必要的邪恶,我必须强烈反对 Denis,是的,goto 在大多数情况下可能是一个坏主意,但有用途。 goto 最大的恐惧是所谓的“spagetti-code”,即无法追踪的代码路径。如果您可以避免这种情况,并且代码的行为方式始终清晰,并且您不会使用 goto 跳出函数,那么 goto 就没有什么问题。请谨慎使用它,如果您想使用它,请认真评估情况并找到更好的解决方案。如果你无法做到这一点,可以使用 goto。

Goto isn't neccessary evil, and I have to strongly disagree with Denis, yes goto might be a bad idea in most cases, but there are uses. The biggest fear with goto is so called "spagetti-code", untraceable code paths. If you can avoid that and if it will always be clear how the code behaves and you don't jump out of the function with a goto, there is nothing against goto. Just use it with caution and if you are tempted to use it, really evaluate the situation and find a better solution. If you unable to do this, goto can be used.

计㈡愣 2024-09-14 12:03:19

避免使用 goto 除非增加(避免)的复杂性更加令人困惑。

在实际的工程问题中,很少使用 goto。学者和非工程师都对使用 goto 感到不必要的烦恼。也就是说,如果您将自己陷入了一个实施困境,其中大量 goto 是唯一的出路,请重新考虑解决方案。

正确工作的解决方案通常是首要目标。使其正确可维护(通过最小化复杂性)具有许多生命周期优势。首先让它发挥作用,然后逐渐清理它,最好是通过简化和消除丑陋的方式。

Avoid goto unless the complexity added (to avoid) is more confusing.

In practical engineering problems, there's room for goto used very sparingly. Academics and non-engineers wring their fingers needlessly over using goto. That said, if you paint yourself into an implementation corner where a lot of goto is the only way out, rethink the solution.

A correctly working solution is usually the primary objective. Making it correct and maintainable (by minimizing complexity) has many life cycle benefits. Make it work first, and then clean it up gradually, preferably by simplifying and removing ugliness.

旧人哭 2024-09-14 12:03:19

我不知道您的具体代码,但是是否有类似这样的原因:

typedef enum {
    STATE1, STATE2, STATE3
} myState_e;

void myFsm(void)
{
    myState_e State = STATE1;

    while(1)
    {
        switch(State)
        {
            case STATE1:
                State = STATE2;
                break;
            case STATE2:
                State = STATE3;
                break;
            case STATE3:
                State = STATE1;
                break;
        }
    }
}

对您不起作用?它不使用 goto,并且相对容易理解。

编辑:所有这些State =片段都违反了DRY,所以我可能会做类似的事情:

typedef int (*myStateFn_t)(int OldState);

int myStateFn_Reset(int OldState, void *ObjP);
int myStateFn_Start(int OldState, void *ObjP);
int myStateFn_Process(int OldState, void *ObjP);

myStateFn_t myStateFns[] = {
#define MY_STATE_RESET 0
   myStateFn_Reset,
#define MY_STATE_START 1
   myStateFn_Start,
#define MY_STATE_PROCESS 2
   myStateFn_Process
}

int myStateFn_Reset(int OldState, void *ObjP)
{
    return shouldStart(ObjP) ? MY_STATE_START : MY_STATE_RESET;
}

int myStateFn_Start(int OldState, void *ObjP)
{
    resetState(ObjP);
    return MY_STATE_PROCESS;
}

int myStateFn_Process(int OldState, void *ObjP)
{
    return (process(ObjP) == DONE) ? MY_STATE_RESET : MY_STATE_PROCESS;
}

int stateValid(int StateFnSize, int State)
{
    return (State >= 0 && State < StateFnSize);
}

int stateFnRunOne(myStateFn_t StateFns, int StateFnSize, int State, void *ObjP)
{
    return StateFns[OldState])(State, ObjP);
}

void stateFnRun(myStateFn_t StateFns, int StateFnSize, int CurState, void *ObjP)
{
    int NextState;

    while(stateValid(CurState))
    {
        NextState = stateFnRunOne(StateFns, StateFnSize, CurState, ObjP);
        if(! stateValid(NextState))
            LOG_THIS(CurState, NextState);
        CurState = NextState;
    }
}

当然,这比第一次尝试要长得多(DRY的有趣之处) 。但它也更强大 - 无法从状态函数之一返回状态将导致编译器警告,而不是默默地忽略早期代码中缺少的 State =

I don't know your specific code, but is there a reason something like this:

typedef enum {
    STATE1, STATE2, STATE3
} myState_e;

void myFsm(void)
{
    myState_e State = STATE1;

    while(1)
    {
        switch(State)
        {
            case STATE1:
                State = STATE2;
                break;
            case STATE2:
                State = STATE3;
                break;
            case STATE3:
                State = STATE1;
                break;
        }
    }
}

wouldn't work for you? It doesn't use goto, and is relatively easy to follow.

Edit: All those State = fragments violate DRY, so I might instead do something like:

typedef int (*myStateFn_t)(int OldState);

int myStateFn_Reset(int OldState, void *ObjP);
int myStateFn_Start(int OldState, void *ObjP);
int myStateFn_Process(int OldState, void *ObjP);

myStateFn_t myStateFns[] = {
#define MY_STATE_RESET 0
   myStateFn_Reset,
#define MY_STATE_START 1
   myStateFn_Start,
#define MY_STATE_PROCESS 2
   myStateFn_Process
}

int myStateFn_Reset(int OldState, void *ObjP)
{
    return shouldStart(ObjP) ? MY_STATE_START : MY_STATE_RESET;
}

int myStateFn_Start(int OldState, void *ObjP)
{
    resetState(ObjP);
    return MY_STATE_PROCESS;
}

int myStateFn_Process(int OldState, void *ObjP)
{
    return (process(ObjP) == DONE) ? MY_STATE_RESET : MY_STATE_PROCESS;
}

int stateValid(int StateFnSize, int State)
{
    return (State >= 0 && State < StateFnSize);
}

int stateFnRunOne(myStateFn_t StateFns, int StateFnSize, int State, void *ObjP)
{
    return StateFns[OldState])(State, ObjP);
}

void stateFnRun(myStateFn_t StateFns, int StateFnSize, int CurState, void *ObjP)
{
    int NextState;

    while(stateValid(CurState))
    {
        NextState = stateFnRunOne(StateFns, StateFnSize, CurState, ObjP);
        if(! stateValid(NextState))
            LOG_THIS(CurState, NextState);
        CurState = NextState;
    }
}

which is, of course, much longer than the first attempt (funny thing about DRY). But it's also more robust - failure to return the state from one of the state functions will result in a compiler warning, rather than silently ignore a missing State = in the earlier code.

雨后咖啡店 2024-09-14 12:03:19

我会向您推荐“龙书”:编译器、原理-技术-工具,来自 Aho、Sethi 和 Ullman。 (购买起来相当昂贵,但您肯定会在图书馆找到它)。在那里你会找到解析字符串和构建有限自动机所需的任何东西。我无法通过goto找到任何地方。通常状态是数据表,转换是像accept_space()这样的函数

I would recommend you the "Dragon book": Compilers, Principles-Techniques-Tools from Aho, Sethi and Ullman. (It is rather expensive to buy but you for sure will find it in a library). There you will find anything you will need to parse strings and build finite automatons. There is no place I could find with a goto. Usually the states are a data table and transitions are functions like accept_space()

同尘 2024-09-14 12:03:19

我看不出 goto 和 switch 之间有多大区别。我可能更喜欢 switch/while 因为它为您提供了一个保证在 switch 之后执行的地方(您可以在其中添加日志记录并推理您的程序)。使用 GOTO,您只需从一个标签跳转到另一个标签,因此要添加日志记录,您必须将其放在每个标签上。

但除此之外应该没有太大区别。不管怎样,如果你没有把它分解成函数,并且不是每个状态都使用/初始化所有局部变量,你可能会得到一堆几乎像意大利面条一样的代码,不知道哪些状态改变了哪些变量,并使调试/推理变得非常困难关于。

顺便说一句,您可以使用正则表达式解析字符串吗?大多数编程语言都有允许使用它们的库。正则表达式通常会创建 FSM 作为其实现的一部分。一般来说,正则表达式适用于非任意嵌套的项目,而对于其他所有内容,都有一个解析器生成器(ANTLR/YACC/LEX)。通常,维护语法/正则表达式比维护底层状态机要容易得多。您还说您正在实习,通常他们可能会给您比高级开发人员更轻松的工作,因此正则表达式很有可能在字符串上工作。此外,大学里通常不强调正则表达式,因此请尝试使用 Google 来阅读它们。

I can't see much of a difference between goto and switch. I might prefer switch/while because it gives you a place guaranteed to execute after the switch (where you could throw in logging and reason about your program). With GOTO you just keep jumping from label to label, so to throw in logging you'd have to put it at every label.

But aside from that there shouldn't be much difference. Either way, if you didn't break it up into functions and not every state uses/initializes all local variables you may end up with a mess of almost spaghetti code not knowing which states changed which variables and making it very difficult to debug/reason about.

As an aside, can you maybe parse the string using a regular expression? Most programming languages have libraries that allow using them. The regular expressions often create an FSM as part of their implementation. Generally regular expressions work for non arbitrarily nested items and for everything else there is a parser generator(ANTLR/YACC/LEX). It is generally much easier to maintain a grammar/regex than the underlying state machine. Also you said you were on an internship, and generally they might give you easier work than say a senior developer, so there is a strong chance that a regex may work on the string. Also regular expressions generally aren't emphasized in college so try using Google to read up on them.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文