学C的时候,老师整天跟我说:“不要用goto,那是坏习惯,丑陋,危险!” 等等。
那么,为什么一些内核程序员会使用goto
,例如在这个函数中,它可以用一个简单的替换
while(condition) {}
或者
do {} while(condition);
我无法理解。while
在某些情况下使用 goto 而不是/ do
-会更好while
吗?如果是这样,为什么?
历史背景:我们应该记住,Dijkstra 在 1968 年写了Goto 被认为是有害的,当时许多程序员将其用作结构化编程(、、等) goto
的替代品。if
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;
生成的代码基本相同,因此我们可以将其视为排版问题,例如缩进。
在这个例子中,我怀疑它是关于将 SMP 支持改进到最初以非 SMP 安全方式编写的代码中。与重构函数相比,添加goto again;
路径要简单得多,侵入性也更小。
我不能说我很喜欢这种风格,但我也认为goto
出于意识形态原因避免这种风格是被误导的。一种特殊的goto
使用情况(与此示例不同)是 wheregoto
仅用于在函数中向前移动,从不向后移动。这类用法永远不会导致由goto
.
非常好的问题,我认为只有作者才能提供明确的答案。正如@Izkata 所解释的那样,我将通过说它可以开始使用它进行错误处理来添加我的一点猜测,然后大门也可以将其用于基本循环。
在我看来,错误处理用法在系统编程中是合法的。一个函数在执行过程中会逐渐分配内存,如果遇到错误,它将goto
使用适当的标签从该点以相反的顺序释放资源。
因此,如果在第一次分配之后发生错误,它将跳转到最后一个错误标签,仅释放一个资源。同样,如果错误发生在最后一次分配之后,它将跳转到第一个错误标签并从那里运行,释放所有资源直到函数结束。这种错误处理模式仍然需要谨慎使用,特别是在修改代码时,强烈建议使用 valgrind 和单元测试。但可以说它比其他方法更具可读性和可维护性。
使用的一个黄金法则goto
是避免所谓的意大利面条代码。尝试在每个goto
语句及其各自的标签之间画线。如果你有交叉线,那么你已经越过了一条线:)。这种用法goto
非常难以阅读,并且是难以跟踪的错误的常见来源,因为它们会在依赖它进行流控制的 BASIC 等语言中找到。
如果你只做一个简单的循环,你就不会越线,所以它仍然是可读和可维护的,主要是风格问题。也就是说,因为它们可以很容易地使用语言提供的循环关键字来完成,正如您在问题中所指出的那样,我的建议仍然是避免使用goto
for 循环,仅仅是因为for
, do/while
orwhile
构造在设计上更加优雅。