18

我在 C 程序中找到了以下代码:

while (1)
{
    do_something();
    if (was_an_error()) break;

     do_something_else();
     if (was_an_error()) break;

     [...]

     break;
}
[cleanup code]

这里while(1)用作“finally”的本地仿真。你也可以用gotos 来写:

do_something()
if (was_an_error()) goto out;

do_something_else()
if (was_an_error()) goto out;

[...]
out:
[cleanup code]

我认为 goto 解决方案是一个常见的习惯用法。我在内核源代码中看到过多次出现这种习惯用法,并且在 Diomidis Spinellis 的“代码阅读”一书中也提到了它。

我的问题是:什么解决方案更好?使用该解决方案是否有任何具体原因while(1)

问题943826没有回答我的问题。

4

13 回答 13

34

对 GOTO 的普遍反感很大程度上是由于 Edsger Dijkstra 的信“Go To Statement Considered Harmful”。

如果你决定不使用 goto,比如

do {
    ...
while(0);

可能比 while(1) { ... } 更安全,因为它保证您不会无意循环(如果您无意循环,使用 while(1) 您可能会无意中无限循环)。

(ab)为此目的使用 do/break/while 或 while/break 优于 goto 的一个优点是,您可以保证不会跳到结构之上—— goto 可用于在同一个标​​签中更早地跳转到一个标签功能。

do/break/while 等相对于 goto 的缺点是您被限制在一个退出点(紧接在循环之后)。在某些情况下,您可能需要分阶段清理:例如,当您打开文件句柄时,malloc 一些内存,从文件中读取...如果读取失败,您需要清理 malloc。如果 malloc 失败,你不需要清理它,但你仍然需要清理文件句柄。使用 goto,您可以在每个清理阶段使用一个标签,并根据错误发生的位置准确地跳转到正确的点。

在我看来,由于对 GOTO 的普遍仇恨而盲目地避免 GOTO 比在逐个案例的基础上仔细推理一个案例的使用更具破坏性。我使用的一条经验法则是“Linux 内核能做到吗?如果是的话,它不会那么糟糕”。用任何其他现代软件工程的好例子代替 linux 内核。

于 2009-07-02T10:18:45.970 回答
10

将代码放入单独的函数中,并使用return提前退出是另一种方法,其好处是可以轻松集成指示故障性质的返回代码。

于 2009-07-02T09:44:57.787 回答
8

我知道我的风格可能不是最酷的,但我更喜欢它,因为它不需要任何特殊的构造,而且简洁且不难理解:

错误 = (!error) && do_something1();
错误 = (!error) && do_something2();
错误 = (!error) && do_something3();

// 清理代码
于 2009-07-02T09:48:09.060 回答
7

尽管通常不鼓励使用 goto,但在像您这样的一些罕见情况下,最佳实践并不是最好的。

所以,如果 goto 编写了最清晰的代码,我会使用它。使用 while(true) 循环来模拟 goto 是不自然的。你真正需要的是一个 goto!

于 2009-07-02T09:35:00.457 回答
5

为什么不使用一系列if语句?我通常这样写,因为我发现它比循环更清晰:

bool ok = true;

do_something();
if (was_an_error()) ok = false;

if (ok)
{
    do_something_else();
    if (was_an_error()) ok = false;
}

if (ok)
{
    do_something_else_again();
    if (was_an_error()) ok = false;
}

[...]

[Cleanup code]

此外,如果您正在遵循严格的编码标准,yesgoto可能会被禁止,但通常也是如此breakcontinue因此循环不一定是解决此问题的方法。

于 2009-07-02T10:41:57.437 回答
5

“break”理解块作用域的语义,而“goto”则忽略了它。换句话说,“while-break”可以翻译成函数式语言,比如带有尾递归的 Lisp,“goto”不能。

于 2009-07-08T23:28:47.400 回答
3

通常,GOTO 被认为是糟糕的,但在某些只有通过 GOTO 向前跳转的地方,它们并没有那么糟糕。人们避免像瘟疫一样避免 GOTO,但经过深思熟虑的 GOTO 使用有时是一个更好的解决方案恕我直言。

于 2009-07-02T09:38:09.750 回答
2

我认为 goto 的这种使用(用于资源管理)是可以的。

于 2009-07-02T09:41:14.970 回答
2

如果您因任何原因无法使用 goto,请使用它

  • 在您的项目约定中被禁止
  • 被你的 lint 工具禁止

我也认为这也是宏不邪恶的情况之一:

#define DO_ONCE for (int _once_dummy = 0; _once_dummy < 1; _once_dummy++)
于 2009-07-02T09:56:10.010 回答
1

“do while”和“goto out”在这些领域是不同的:

1.局部变量初始化

void foo(bool t = false)
{
    if (t)
    {
        goto DONE;
    }

    int a = 10; // error : Goto bypass local variable's initialization 

    cout << "a=" << a << "\n";
DONE:
}

可以在 do ... while(0) 块中初始化就地局部变量。

void bar(bool t = false)
{
    do{
        if (t)
        {
            break; 
        }

        int a = 10;  // fine

        cout << "a=" << a << "\n";
    } while (0);

}

2 宏的区别。“do while”稍微好一点。宏中的“goto DONE”并非如此。如果退出代码更复杂,让我们看看:

err = some_func(...);
if (err)
{
    register_err(err, __LINE__, __FUNC__);
#if defined (_DEBUG)
    do_some_debug(err)
#endif
    break;
}

而你一次又一次地编写这段代码,你可能会将它们放入一个宏中。

#define QUIT_IF(err)                     \
if (err)                                       \
{                                              \
    register_err(err, __LINE__, __FUNC__);     \
    DO_SOME_DEBUG(err)                         \
    break; // awful to put break in macro, but even worse to put "goto DONE" in macro.  \
}

代码变为:

do
{
    initial();

    do 
    {
        err = do_step1();
        QUIT_IF(err);

        err = do_step2();
        QUIT_IF(err);

        err = do_step3();
        QUIT_IF(err);

        ....
    } while (0);
    if (err) {     // harder for "goto DONE" to get here while still using macro.
        err = do_something_else();
    }
    QUIT_IF(err);
    .....
} while (0);

3.do... while(0) 使用相同的宏处理不同级别的退出。代码如上所示。goto ... 不是 Macro 的情况,因为您需要不同级别的不同标签。

这么说,我两个都不喜欢。我更喜欢使用异常方法。如果不允许异常,那么我使用“do ... while(0)”,因为整个块是缩进的,实际上它比“goto DONE”样式更容易阅读。

于 2013-08-29T17:37:56.347 回答
1

我喜欢 while(1) 方法。我自己用它。特别是当循环可能被 continue 重复时,例如当一个元素在这样的循环中被处理时,并且它以多种方法完成。

于 2009-07-02T09:41:51.303 回答
1

永远不要使用具有永久真实条件的条件循环。既然条件总是为真,为什么要使用条件循环呢?

永久正确的条件最直接地由 goto 表示。

于 2009-07-02T09:41:55.297 回答
0

虽然在错误处理情况下使用“goto”相当普遍,但我仍然更喜欢“while”解决方案(或“do while”)。在“goto”的情况下,编译器可以保证的事情要少得多。如果您在标签名称中打错字,编译器将无法帮助您。如果有人在该块中使用另一个 goto 到另一个标签,则很有可能不会调用清理代码。当您使用更结构化的流控制结构时,您始终可以保证在循环结束后将运行哪些代码。

于 2009-07-02T09:45:08.037 回答