当我学习C时,老师终日告诉我:“不要使用goto,这是一个坏习惯,它很难看,这很危险!”等等。
为什么然后,一些内核程序员使用goto
,例如in this function,它可以用简单的
while(condition) {}
或
do {} while(condition);
我无法理解。在某些情况下使用goto而不是while
/ do
- while
会更好吗?如果是这样,为什么?
答案 0 :(得分:56)
历史背景我们应该记住,Dijkstra在1968年写了 Goto Considered Harmful ,当时许多程序员使用goto
代替if
{3}}(while
,for
,goto
等)。
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 VALUES
和SETUP
,这强调了大多数情况下,控制流都通过两者。结构化版本将COMPUTE SOME VALUES
和goto
放在不同的列上,强调控件可能以不同方式传递它们。
这里的问题是您希望在代码中加入什么样的重点?您可以将此与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} },for
或do/while
构造在设计上更加优雅。