2009-07-02 111 views
17

我发现在C程序以下代码:而(1)..打破,而不是转到

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

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

    [...] 

    break; 
} 
[cleanup code] 

while(1)这里被用作“最后”本地仿真。你也可以这样写使用goto S:

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

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

[...] 
out: 
[cleanup code] 

我以为转到解决方案是一个常用的成语。我已经在内核源码中看到过这种成语的几个例子,并且在Diomidis Spinellis的“代码阅读”一书中也提到了它。

我的问题是:什么解决方案更好?是否有任何特定的理由使用while(1)解决方案?

问题943826不回答我的问题。

+20

使用while(1)使得读取代码的人期望它运行“无限”......为什么不使用do {} while(0)? – 2009-07-02 09:39:02

+1

@Tal Pressman - 为什么会有所不同? – 2009-07-02 09:40:51

+2

@Earwicker,用这个选项很明显它至少运行一次,并且它不会再运行,因为条件是“while(0)”。混淆的“while(1)”没有出现。 – 2009-07-02 09:44:55

回答

33

看似普遍revultion到GOTO很大程度上是由于Edsger Dijkstra的字母“goto语句是有害的”。

如果你坚决不使用goto语句,像

do { 
    ... 
while(0); 

可能比同时更安全(1){...},因为它保证你不会在不经意间循环(如果你是在不经意间循环,while(1)你可能无意中循环无限)。 (ab)使用do/break/while或while/break来达到此目的的优点之一就是你保证不会跳跃以上构造 - goto可以用来跳转到在同一个函数内的一个标签。

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

在我看来,盲目避免GOTO是因为它普遍存在的仇恨,它比根据具体情况慎重推理案例更具破坏性。我使用的经验法则是“Linux内核是否这样做?如果是这样,它不可能是坏”。用现代软件工程的其他很好的例子替代linux内核。

7

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

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

+2

+1使用GOTO在嵌入式系统中很常见,可以在故障后进行清理。 – 2009-07-02 09:57:32

+2

+1使用goto不会因为“只是因为”而灰心。避免使用意大利细面条代码并提高清晰度令人沮丧。真正的规则是功能应该清晰和简单,所以使用更难以掌握替代goto不是一个好的选择国际海事组织。任何涉及一次循环的事物都属于这一类。 – 2009-07-02 10:00:13

3

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

2

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

1

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

1

切勿使用具有永久真实条件的条件循环。由于条件总是如此,为什么使用条件循环?

永久的真实条件最直接代表一个转到。

10

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

0

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

7

我知道我的风格是不是最酷的可能,但我更喜欢它,因为它不需要任何特殊的结构和简洁并不难懂:

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

// Cleanup code 
2

如果你可以用它“T使用无论出于何种原因

  • 在项目的约定不得转到
  • 您的皮棉工具禁止

我也觉得也是其中宏不是邪恶的情况之一:

#define DO_ONCE for (int _once_dummy = 0; _once_dummy < 1; _once_dummy++) 
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

“break”理解块范围的语义,而“goto”不理会它。换句话说,“while-break”可以被翻译成功能语言,比如带尾递归的Lisp,“goto”不能。

0

“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宏的差异。 “尽管”稍微好一点。宏中的“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 ......而(0)处理不同级别相同的宏退出的。代码如上所示。转到...宏不是这种情况,因为你需要不同级别的不同标签。

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