每个人都知道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只是为了它而包含的东西非常低。这并不意味着这是不可接受的。

Goto可以很好地用于状态机。循环中的switch语句(按典型重要性排序):(A)不能实际代表控制流,(b)丑陋,(c)可能效率低下,这取决于语言和编译器。因此,您最终为每个状态编写一个函数,并执行类似“return NEXT_STATE;”的操作,这甚至看起来像goto。

当然,以易于理解的方式对状态机进行编码是很困难的。然而,这些困难都与使用goto无关,也不能通过使用替代控制结构来减少。除非你的语言有一个“状态机”结构。我不喜欢。

在极少数情况下,当您的算法通过通过有限的允许转换集(goto)连接的节点(状态)序列的路径而不是通过任何更具体的控制流(循环、条件等等)来理解时,那么应该在代码中显式地说明这一点。你应该画一个漂亮的图。

Setjmp /longjmp可以很好地实现异常或类似异常的行为。虽然没有得到普遍的赞扬,但异常通常被认为是一种“有效的”控制结构。

Setjmp /longjmp比goto“更危险”,因为它们更难正确使用,更不用说理解了。

从来没有,将来也不会有 永远是,任何语言都是 写不好一点也难 代码。——唐纳德·克努特

从C中去掉goto并不会使用C编写好的代码变得更容易。事实上,这样做反而会忽略一点,即C应该能够作为一种被美化的汇编语言。

接下来是“有害的指针”,然后是“有害的鸭子类型”。那么,当他们来拿走你不安全的编程结构时,谁来保护你呢?是吗?

其他回答

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

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

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

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

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

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

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

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

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

在我编程生涯的早期,有一次,我编写了一个由一系列链函数组成的程序,其中每个函数在给定成功条件和完成条件的情况下调用它的后继函数。

这是一个丑陋的笨拙,有许多严重的问题,最严重的是,在它下面的所有函数都终止之前,任何函数都不能终止。

但是它很快就被开发出来了,对于它所要解决的有限的问题集工作得很好,并且明确地显示了程序的逻辑和流程,当我对它进行重构和扩展以包含在另一个项目中时,它工作得很好。

我的意见是在有意义的时候使用它,并在方便的时候尽快重构它。

Go To可以在某些情况下为“真正的”异常处理提供一种替代品。考虑:

ptr = malloc(size);
if (!ptr) goto label_fail;
bytes_in = read(f_in,ptr,size);
if (bytes_in=<0) goto label_fail;
bytes_out = write(f_out,ptr,bytes_in);
if (bytes_out != bytes_in) goto label_fail;

显然,这段代码被简化了,以占用更少的空间,所以不要太纠结于细节。但是考虑一下我在产品代码中多次看到的另一种选择,即程序员为了避免使用goto而费尽心机:

success=false;
do {
    ptr = malloc(size);
    if (!ptr) break;
    bytes_in = read(f_in,ptr,size);
    if (count=<0) break;
    bytes_out = write(f_out,ptr,bytes_in);
    if (bytes_out != bytes_in) break;
    success = true;
} while (false);

现在这段代码在功能上做了完全相同的事情。事实上,编译器生成的代码几乎完全相同。然而,在程序员对Nogoto(可怕的学术指责之神)的热情中,这个程序员完全打破了while循环所代表的底层习惯,并对代码的可读性造成了实质性的影响。这样也好不到哪里去。

所以,这个故事的寓意是,如果你发现自己为了避免使用goto而求助于一些非常愚蠢的事情,那么就不要这样做。

在我所见过的每个平台上,高级控制结构都被实现为低级的goto(跳转)。例如,Java虚拟机有一个跳转字节码,但是没有if、else、while、For等字节码。

其中一些编译器会为一个简单的条件块创建意大利面条代码。

回答你的问题,去走仍然被那些认为它有害的人认为是有害的。Goto很容易失去结构化编程的优点。

最后,这是你的程序;所以你的决定。我建议在您能够自己回答问题之前不要使用goto,而是在特定问题的上下文中使用。