而(1)..打破而不是转到

时间:2009-07-02 09:29:52

标签: c goto

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

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

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

     [...]

     break;
}
[cleanup code]

此处while(1)用作“finally”的本地模拟。您也可以使用goto s:

来编写此内容
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没有回答我的问题。

13 个答案:

答案 0 :(得分:33)

对GOTO的看似普遍的反驳很大程度上归功于Edsger Dijkstra的信“Go To Statement Considered harmful”。

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

do {
    ...
while(0);

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

(ab)为此目的使用do / break / while或while / break的一个优点是,你可以保证不会跳过以上构造 - goto可以用于在同一函数中提前跳转到标签。

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

在我看来,盲目地避免使用GOTO是因为它普遍存在仇恨,而不是根据具体情况仔细推理一个案例。我使用的经验法则是“Linux内核是否做到了?如果是这样,它就不能 坏”。用任何其他现代软件工程的好例子替换linux内核。

答案 1 :(得分:10)

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

答案 2 :(得分:7)

虽然通常不鼓励使用goto,但像你这样的一些罕见情况是最佳做法不是最好的地方。

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

答案 3 :(得分:7)

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

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

// Cleanup code

答案 4 :(得分: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]

此外,如果您正在努力达到严格的编码标准,可能会禁止goto,但breakcontinue通常也是如此,因此循环不一定是解决方法

答案 5 :(得分:5)

“break”理解块范围的语义,而“goto”则忽略它。换句话说,“while-break”可以翻译成像Lisp这样的函数式语言,尾部递归,“goto”不能。

答案 6 :(得分:3)

通常情况下,GOTO被认为是坏的,但在某些只有通过GOTO进行前向跳跃的地方,它们并不差。人们避免GOTO像瘟疫一样,但经过深思熟虑的GOTO使用有时是更好的解决方案恕我直言。

答案 7 :(得分:2)

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

答案 8 :(得分:2)

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

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

我也认为这也是宏不是邪恶的案例之一:

#define DO_ONCE for (int _once_dummy = 0; _once_dummy < 1; _once_dummy++)

答案 9 :(得分:1)

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

答案 10 :(得分:1)

永远不要使用具有永久真实条件的条件循环。由于条件始终为真,为什么要使用条件循环?

永久真实条件最直接由goto表示。

答案 11 :(得分:0)

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

答案 12 :(得分:0)

“do while”和“goto out”在这些区域有所不同:

1.local变量初始化

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)使用相同的宏处理不同的退出级别。代码如上所示。转到...不是宏的情况,因为您需要不同级别的不同标签。

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