为什么有些内核程序员使用goto而不是简单的while循环?

时间:2012-10-21 18:58:02

标签: c linux loops linux-kernel goto

当我学习C时,老师终日告诉我:“不要使用goto,这是一个坏习惯,它很难看,这很危险!”等等。

为什么然后,一些内核程序员使用goto,例如in this function,它可以用简单的

替换
while(condition) {} 

do {} while(condition);

我无法理解。在某些情况下使用goto而不是while / do - while会更好吗?如果是这样,为什么?

3 个答案:

答案 0 :(得分:56)

历史背景我们应该记住,Dijkstra在1968年写了 Goto Considered Harmful ,当时许多程序员使用goto代替if {3}}(whileforgoto等)。

44年过去了,在野外很少发现 SETUP... again: COMPUTE SOME VALUES... if (cmpxchg64(ptr, old_val, val) != old_val) goto again; 的使用。结构化编程早就赢了。

案例分析:

示例代码如下所示:

SETUP...
do {
    COMPUTE SOME VALUES...
} while (cmpxchg64(ptr, old_val, val) != old_val);

结构化版本如下所示:

goto

当我看结构化版本时,我立刻想到,“这是一个循环”。当我查看goto版本时,我认为它是一条直线,最后会再次尝试使用。

SETUP版本在同一列上同时包含COMPUTE SOME VALUESSETUP,这强调了大多数情况下,控制流都通过两者。结构化版本将COMPUTE SOME VALUESgoto放在不同的列上,强调控件可能以不同方式传递它们。

这里的问题是您希望在代码中加入什么样的重点?您可以将此与if (do_something() != ERR) { if (do_something2() != ERR) { if (do_something3() != ERR) { if (do_something4() != ERR) { ... 进行比较以进行错误处理:

结构化版本:

if (do_something() == ERR)  // Straight line
    goto error;             // |
if (do_something2() == ERR) // |
    goto error;             // |
if (do_something3() == ERR) // |
    goto error;             // V
if (do_something4() == ERR) // emphasizes normal control flow
    goto error;

转到版本:

{{1}}

生成的代码基本相同,因此我们可以将其视为印刷问题,如缩进。

答案 1 :(得分:27)

在本例中,我怀疑是将SMP支持改进为最初以非SMP安全方式编写的代码。添加goto again;路径比重构功能要简单得多,侵入性也小。

我不能说我喜欢这种风格,但我也认为出于意识形态的原因而避免goto被误导。 goto用法的一个特例(与此示例不同)是goto仅用于在函数中向前移动,而不是向后移动。这类用法永远不会产生由goto引起的循环结构,并且它几乎总是最简单,最清晰的方式来实现所需的行为(通常是清理并返回错误)。

答案 2 :(得分:0)

很好的问题,我认为只有作者才能提供明确的答案。我会说些推测,说它可能开始使用它来进行错误处理,正如@Izkata所解释的,然后大门也开放了用于基本循环的机会。

在我看来,错误处理的用法在系统编程中是合法的。函数会随着进程的进行逐渐分配内存,如果遇到错误,它将goto适当的标签从该点开始以相反的顺序释放资源。

因此,如果在第一次分配后发生错误,它将跳至最后一个错误标签,从而仅释放一个资源。同样,如果错误在最后一次分配之后发生,它将跳到第一个错误标签并从那里开始运行,释放所有资源,直到函数结束。仍然需要谨慎使用这种错误处理模式,尤其是在修改代码时,强烈建议使用valgrind和单元测试。但是可以说,它比其他方法更具可读性和可维护性。

使用goto的一条黄金法则是避免使用所谓的意大利面条式代码。尝试在每个goto语句及其各自的标签之间绘制线条。如果您有划线,那么您已经划线了:)。 goto的这种用法很难阅读,并且是难以跟踪错误的常见来源,因为它们可以在依赖BASIC之类的语言进行流控制的语言中找到。

如果只执行一个简单的循环,就不会产生交叉线,因此它仍然可读且可维护,很大程度上取决于样式。就是说,由于可以使用提供的循环关键字来轻松完成它们,正如您在问题中所指出的那样,我的建议仍然是避免将goto用于循环,仅因为{{1} },fordo/while构造在设计上更加优雅。