我一直有一个印象,如果可能的话,永远不要使用goto。

然而,在前几天阅读libavcodec(它是用C编写的)时,我惊讶地注意到它的多种用法。

在支持循环和函数的语言中使用goto是否有优势?如果有,为什么?请提供一个具体的例子,清楚地说明使用goto的理由。


当前回答

我们使用的goto规则是,goto可以跳转到函数中的单个退出清理点。在真正复杂的函数中,我们放松了这个规则,允许其他跳转。在这两种情况下,我们都避免了经常在错误代码检查中出现的深度嵌套的if语句,这有助于可读性和维护。

其他回答

下面是我所知道的使用“goto”语句的一些原因(有些人已经谈到了这个问题):

干净地退出函数

通常在一个函数中,您可能会分配资源并需要在多个位置退出。程序员可以通过将资源清理代码放在函数的末尾来简化他们的代码,并且函数的所有“出口点”都将进入清理标签。这样,您就不必在函数的每个“退出点”都编写清理代码。

退出嵌套循环

如果处于嵌套循环中,需要跳出所有循环,那么goto可以比break语句和If -checks更简洁。

低水平的性能改进

这只在对性能要求严格的代码中有效,但是goto语句执行得非常快,并且可以在遍历函数时提高性能。然而,这是一把双刃剑,因为编译器通常不能优化包含goto的代码。

注意,在所有这些示例中,gotos都被限制在单个函数的范围内。

盲目地遵循最佳实践并不是最佳实践。避免将goto语句作为流控制的主要形式是为了避免产生难以阅读的意大利面条代码。如果在正确的地方谨慎使用,它们有时可以成为表达想法的最简单、最清晰的方式。Walter Bright, Zortech c++编译器和D编程语言的创造者,经常使用它们,但很明智。即使使用了goto语句,他的代码仍然完全可读。

底线:为了避免去那里而避免去那里是毫无意义的。您真正需要避免的是生成不可读的代码。如果充满goto的代码是可读的,那么它就没有任何问题。

当然,可以使用GOTO,但是有一件事比代码风格更重要,或者在使用它时,您必须考虑到代码是否可读:其中的代码可能不像您想象的那样健壮。

例如,看看下面的两个代码片段:

If A <> 0 Then A = 0 EndIf
Write("Value of A:" + A)

GOTO的等效代码

If A == 0 Then GOTO FINAL EndIf
   A = 0
FINAL:
Write("Value of A:" + A)

我们首先想到的是这两段代码的结果将是“Value of A: 0”(当然,我们假设执行没有并行性)

这是不正确的:在第一个示例中,A将始终为0,但在第二个示例中(使用GOTO语句)A可能不是0。为什么?

原因是,从程序的另一点,我可以插入一个GOTO FINAL而不控制a的值。

这个例子非常明显,但是随着程序变得越来越复杂,看到这些东西的难度也增加了。

相关材料可以在Dijkstra先生的著名文章“反对GO TO声明的案例”中找到

因为goto使得程序流的推理变得困难。“意大利面条代码”),goto通常只用于弥补缺失的功能:使用goto实际上可能是可以接受的,但前提是语言没有提供更结构化的变体来获得相同的目标。以《怀疑》为例:

我们使用的goto规则是,goto可以跳转到函数中的单个退出清理点。

这是对的——但前提是语言不允许使用清理代码进行结构化异常处理(如RAII或finally),后者可以更好地完成相同的工作(因为它是专门为此而构建的),或者有很好的理由不使用结构化异常处理(但除非在非常低的级别,否则您永远不会遇到这种情况)。

在大多数其他语言中,goto唯一可接受的用法是退出嵌套循环。即使在这种情况下,将外部循环提升为自己的方法并使用return也总是更好。

除此之外,goto是对特定代码段考虑不够的标志。


支持goto实现一些限制的现代语言(例如,goto可能不会跳转到函数中或跳出函数),但问题从根本上还是一样的。

顺便说一句,其他语言特性当然也是如此,尤其是例外。而且通常有严格的规则,只在指定的地方使用这些特性,例如不使用异常来控制非异常程序流的规则。

如果有,为什么?

C语言没有多级/标记的中断,并不是所有的控制流都可以用C语言的迭代和决策原语轻松建模。Gotos对纠正这些缺陷大有帮助。

有时使用某种类型的标志变量来实现一种伪多级中断更清晰,但它并不总是优于goto(至少goto可以轻松地确定控制的位置,不像标志变量),有时您只是不想为了避免goto而付出旗帜/其他扭曲的性能代价。

Libavcodec是一段性能敏感的代码。控制流的直接表达可能是优先考虑的,因为它往往会运行得更好。