C中goto的使用

4

我有一个状态机和一些回调机制,任务运行器会调用函数。在此背后,有一个有四种状态的状态机,其中有些互斥,而其他一些可以组合,这使得规则集相当复杂。如果尝试任何非法操作,则其中一个函数应该向用户显示错误消息(此处为了简单起见使用 printf):

static int state1 = 0;
static bool switch2 = 1;

void do_stuff(int value){
    int errorCode = 0;
    if(state1 == 1){ 
        errorCode = -1;
        goto ERROR;
    }
    if(state1 == 2 && switch2)
    {
        errorCode = 2;
        goto ERROR;
    }


    printf("No error!");
    return;
ERROR:
    printf("%d", errorCode);
}

这是我能想到的最短、最简明的方式,但一直强调使用goto是件坏事。有没有更好的解决方法?或者这是在稳定性和可维护性方面最好的方式了呢?

4
使用goto语句来实现状态机是可以接受的(躲起来不被攻击)。但你的代码混合了状态变量和goto语句,我不喜欢这样。 - john
3
你不仅在滥用 goto,而且你所做的只是一个低劣版本的异常处理。定义自己的异常类,其中包括你的 errorCode 值并进行 throw 抛出。 - tadman
@Jarod42,你能给个例子吗?我不太理解。 - Curunir
1
@EdHeal,我重新标记了这个问题,因为代码不是C++。 - SergeyA
1
“使用goto是一件坏事”是一个普遍存在的过度概括,这是令人遗憾的。在C和尤其是C++中总是有替代方案。通常情况下,特别是在C++中,有更好的替代方案。然而,在“通常”和“总是”之间的空白中,有些情况下,谨慎、策略性地使用goto是可用的最清晰、最明确的替代方案。因为教条主义的信念认为goto是不好的,而拒绝了最干净、最清晰的代码,这不是一个理性的决定。 - John Bollinger
显示剩余8条评论
2个回答

8
"goto很少是控制流程的正确解决方案。虽然在某些情况下使用goto是合理的,但在这个特定的函数中,您可以将控制流重构为像这样的if-else分支:"
void do_stuff(int value)
{
    int errorCode = 0;

    if (state1 == 1)
    { 
        errorCode = -1;
    }
    else if (state1 == 2 && switch2)
    {
        errorCode = 2;
    }
    else // unconditional case for no errors
    {
        printf("No error!");
        return;
    }
    printf("%s", errorCode);  // if control reaches here, print the error
}

4
他可以。但请指出“goto”存在的问题。我没有看到很大的改进,也没有看到任何针对“goto”的强有力论据。 - Cheatah
@Cheatah 我的回答只是基于(就像问题一样)“goto”是有害的这一前提来回答问题:“是否有更好的方法?”此外,个人认为不使用“goto”确实是一个重大改进。通常情况下,我觉得应该强调使用“goto”的理由,而不是相反。 - cigien
1
@cigien 双方都有可能发生。有时候,你会看到在代码中尽力避免使用 goto 的体操,而实际上使用 goto 可以简化代码。因此,通常取决于具体的用例,而不是需要证明哪种方式更好。 - P.P
@P.P 很好。我倾向于偏向一侧,但我理解你的观点。至少在这种情况下,似乎没有使用goto的理由,而替代方案非常易读。 - cigien
这个解决方案很复杂,只是看起来比较干净,因为在每个错误情况下都没有清理代码。 - Acorn
@Acorn 当然,我只是展示了对于原帖的一个解决方案。如果原帖中的代码不同,那么使用 goto 可能会更加简洁,但这取决于具体的代码。 - cigien

2
据我所知,您正在使用“goto”来处理错误,而不是状态机。
对于错误处理,使用“goto”实际上是非常有用且优选的,而不是使用复杂的条件链。它允许您执行手动RAII而不重复代码:
int do_stuff(...)
{
    ... = f1(...);
    if (...)
        goto ERROR_f1;

    ... = f2(...);
    if (...)
        goto ERROR_f2;

    ... = f3(...);
    if (...)
        goto ERROR_f3;

    // Success
    return ...;

ERROR_f3:
    undo_f2(...);

ERROR_f2:
    undo_f1(...);

ERROR_f1:
    return ...;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接