每个人都知道Dijkstra的《致编辑的信》:goto语句被认为是有害的(这里。html transcript和这里。pdf),从那时起,就有一种强大的推动力,尽可能避免使用goto语句。虽然可以使用goto来生成不可维护的、庞大的代码,但它仍然存在于现代编程语言中。即使Scheme中先进的连续控制结构也可以被描述为复杂的后向。

在什么情况下需要使用goto?什么时候最好避免?

作为一个后续问题:C提供了一对函数setjmp()和longjmp(),它们不仅提供了在当前堆栈帧内进行跳转的能力,还提供了在任何调用帧内进行跳转的能力。这些应该被认为和goto一样危险吗?更危险?


Dijkstra自己也对这个头衔感到后悔,因为这不是他的责任。在EWD1308的结尾(也在这里。pdf),他写道:

Finally a short story for the record. In 1968, the Communications of the ACM published a text of mine under the title "The goto statement considered harmful", which in later years would be most frequently referenced, regrettably, however, often by authors who had seen no more of it than its title, which became a cornerstone of my fame by becoming a template: we would see all sorts of articles under the title "X considered harmful" for almost any X, including one titled "Dijkstra considered harmful". But what had happened? I had submitted a paper under the title "A case against the goto statement", which, in order to speed up its publication, the editor had changed into a "letter to the Editor", and in the process he had given it a new title of his own invention! The editor was Niklaus Wirth.

Donald E. Knuth写了一篇关于这个主题的经典论文,它与Dijkstra的论文相匹配,那就是结构化编程与语句。阅读既有助于重建上下文,也有助于对主题的非教条性理解。在本文中,Dijkstra对这个案例的观点被报道,并且更加强烈:

Donald E. Knuth:我相信通过呈现这样一个 事实上,我并不是不同意 Dijkstra的观点,因为 他最近写道: “请不要落入。 相信我是可怕的 教条的关于[去]的教条的 声明)。我觉得很不舒服 感觉别人在做 宗教出来了,好像 编程的概念问题 只用一个小技巧就能解决,用什么 一种简单的编码纪律!”


当前回答

有时候,在一个函数中使用GOTO作为异常处理的替代是有效的:

    if (f() == false) goto err_cleanup;
    if (g() == false) goto err_cleanup;
    if (h() == false) goto err_cleanup;
    
    return;
    
    err_cleanup:
    ...

COM代码似乎经常陷入这种模式。

其他回答

并不是说去做本身不好;而是当同样的逻辑可以用另一种方式更清楚地表达时,使用goto是不好的。它会使代码很难遵循,也会使维护变得困难。以《糟糕的过去》中的一些Basic程序为例。

在我看来,在像c#这样的现代语言中,我们不应该在正常情况下需要goto。如果我发现自己正在使用它,这通常表明我需要重新思考我的逻辑——几乎肯定有一种更清晰的方式来使用正常的代码流语句来表达相同的代码。

也就是说,对于某些特殊的目的来说,goto是非常有用的(我发现自己对那些没有goto的语言非常恼火)。我主要在C语言中使用它来打破多层循环,或者进行错误处理;我相信c#的语言特性意味着您不必这样做。(它在生成自动生成代码时也非常有用,但大多数人在现实生活中不会遇到这种情况。)

goto还有一个纯粹的政治问题:很多人讨厌它,在代码中使用它,即使是合理的,也可能会引起问题。如果这是作业代码,那么是的,重写它,否则你可能会被扣分。否则,我倾向于把它留在那里,直到下次你需要对那部分进行维护。

被Jay Ballou添加的答案所吸引,我会加入0.02英镑。如果Bruno Ranschaert还没有这样做,我就会提到Knuth的“用GOTO语句进行结构化编程”的文章。

有一件事我没有看到讨论,那就是那种在Fortran教科书中教过的代码,尽管它并不常见。例如DO循环的扩展范围和开放代码子程序(记住,这将是Fortran II, Fortran IV或Fortran 66 -而不是Fortran 77或90)。至少有可能语法细节不准确,但概念应该足够准确。每种情况下的代码片段都在单个函数中。

请注意,由Kernighan和Plauger撰写的优秀但过时(并且绝版)的《编程风格的元素,第二版》中包含了一些来自那个时代(70年代末)编程教科书中滥用GOTO的现实例子。然而,下面的材料并不是来自那本书。

DO循环的扩展范围

       do 10 i = 1,30
           ...blah...
           ...blah...
           if (k.gt.4) goto 37
91         ...blah...
           ...blah...
10     continue
       ...blah...
       return
37     ...some computation...
       goto 91

One reason for such nonsense was the good old-fashioned punch-card. You might notice that the labels (nicely out of sequence because that was canonical style!) are in column 1 (actually, they had to be in columns 1-5) and the code is in columns 7-72 (column 6 was the continuation marker column). Columns 73-80 would be given a sequence number, and there were machines that would sort punch card decks into sequence number order. If you had your program on sequenced cards and needed to add a few cards (lines) into the middle of a loop, you'd have to repunch everything after those extra lines. However, if you replaced one card with the GOTO stuff, you could avoid resequencing all the cards - you just tucked the new cards at the end of the routine with new sequence numbers. Consider it to be the first attempt at 'green computing' - a saving of punch cards (or, more specifically, a saving of retyping labour - and a saving of consequential rekeying errors).

哦,你可能还注意到我在作弊,没有大喊大叫——Fortran IV通常都是大写的。

中非子例程

       ...blah...
       i = 1
       goto 76
123    ...blah...
       ...blah...
       i = 2
       goto 76
79     ...blah...
       ...blah...
       goto 54
       ...blah...
12     continue
       return
76     ...calculate something...
       ...blah...
       goto (123, 79) i
54     ...more calculation...
       goto 12

标签76和54之间的GOTO是计算GOTO的一个版本。如果变量i的值为1,则转到列表中的第一个标签(123);如果它的值是2,就转到秒,以此类推。从76到计算goto的片段是开放编码的子程序。它是一段执行起来很像子例程的代码,但写在函数体中。(Fortran也有语句函数——它们是嵌入在单行上的函数。)

还有比计算goto更糟糕的结构——你可以给变量赋标签,然后使用赋值的goto。google assigned goto告诉我它已经从Fortran 95中删除了。值得注意的是,结构化编程革命可以说是从Dijkstra的“GOTO被认为是有害的”信件或文章开始的。

如果不了解Fortran语言(以及其他语言,其中大多数已经半途而用了)中所做的事情,我们这些新手很难理解Dijkstra所处理的问题的范围。见鬼,直到那封信发表10年后,我才开始编程(但我确实不幸地在Fortran IV中编程了一段时间)。

没有所谓的GOTO被认为是有害的。

GOTO是一种工具,和所有工具一样,它可以被使用和滥用。

然而,在编程世界中有许多工具倾向于被滥用而不是被使用,GOTO就是其中之一。Delphi的WITH语句是另一个。

就我个人而言,我在典型的代码中不使用这两种方法,但我有过GOTO和WITH的奇怪用法,这是有保证的,另一种解决方案将包含更多的代码。

最好的解决方案是编译器只警告你关键字被污染了,你必须在语句周围塞几个pragma指令来消除警告。

这就像告诉你的孩子不要拿着剪刀跑。剪刀并不坏,但使用它们可能不是保持健康的最佳方式。

用于调度的计算gotos通常比非常大的switch语句更容易理解。

对于错误和联合线程,我认为setcontext或setjmp(如果可用)是“更好的”。

在C语言中,goto只在当前函数的范围内工作,这倾向于本地化任何潜在的错误。Setjmp和longjmp要危险得多,它们是非本地的、复杂的、依赖于实现的。然而,在实践中,它们太模糊和不常见,不会引起很多问题。

我认为在C中goto的危险被大大夸大了。请记住,最初的goto参数发生在老式BASIC等语言的时代,初学者会编写这样的意大利面条代码:

3420 IF A > 2 THEN GOTO 1430

这里Linus描述了goto的适当用法:http://www.kernel.org/doc/Documentation/CodingStyle(第7章)。