每个人都知道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的观点,因为 他最近写道: “请不要落入。 相信我是可怕的 教条的关于[去]的教条的 声明)。我觉得很不舒服 感觉别人在做 宗教出来了,好像 编程的概念问题 只用一个小技巧就能解决,用什么 一种简单的编码纪律!”


当前回答

被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,因为我真的想不出更好(更快)的方法来编写这段代码:

我有一个复杂的对象,我需要对它做一些操作。如果对象处于一种状态,那么我就可以进行快速操作,否则我就必须进行慢速操作。问题是,在某些情况下,在缓慢的手术过程中,可能会意识到这可以用快速手术来完成。

SomeObject someObject;    

if (someObject.IsComplex())    // this test is trivial
{
    // begin slow calculations here
    if (result of calculations)
    {
        // just discovered that I could use the fast calculation !
        goto Fast_Calculations;
    }
    // do the rest of the slow calculations here
    return;
}

if (someObject.IsmediumComplex())    // this test is slightly less trivial
{
    Fast_Calculations:
    // Do fast calculations
    return;
}

// object is simple, no calculations needed.

这是一个实时UI代码的速度关键部分,所以我真的认为GOTO在这里是合理的。

Hugo

在Linux:在Kernel Trap的内核代码中使用goto,有一个与Linus Torvalds和一个“新人”关于在Linux代码中使用goto的讨论。有一些非常好的观点,莱纳斯穿着平常的傲慢:)

一些段落:

莱纳斯:不,你被洗脑了 国安局的人认为尼克劳斯 Wirth其实知道自己是什么 说什么。他没有。他不 我有个该死的线索。”

-

莱纳斯:“我觉得goto很好,而且 它们通常比 大量的压痕。”

-

莱纳斯:“当然,用愚蠢的语言 就像帕斯卡,在那里标签不能 描述性的,后去可能很糟糕。”

我们已经讨论过这个问题了,我坚持我的观点。

此外,我受够了人们将高级语言结构描述为“伪装的goto”,因为他们显然根本没有抓住重点。例如:

即使Scheme中先进的连续控制结构也可以被描述为复杂的后向。

那完全是胡说八道。每个控制结构都可以在goto方面实现,但这个观察完全是微不足道和无用的。Goto被认为是有害的,因为它的积极影响,而是因为它的消极后果,这些已经被结构化编程消除了。

Similarly, saying “GOTO is a tool, and as all tools, it can be used and abused” is completely off the mark. No modern construction worker would use a rock and claim it “is a tool.” Rocks have been replaced by hammers. goto has been replaced by control structures. If the construction worker were stranded in the wild without a hammer, of course he would use a rock instead. If a programmer has to use an inferior programming language that doesn't have feature X, well, of course she may have to use goto instead. But if she uses it anywhere else instead of the appropriate language feature she clearly hasn't understood the language properly and uses it wrongly. It's really as simple as that.

从这里的任何答案中我都没有看到的一件事是,“去”解决方案通常比经常提到的结构化编程解决方案更有效。

考虑多嵌套循环的情况,其中使用'goto'而不是一堆if(breakVariable)节显然更有效。“将循环放入函数并使用return”的解决方案通常是完全不合理的。在循环可能使用局部变量的情况下,现在必须通过函数参数传递它们,可能会处理由此产生的额外麻烦。

现在考虑一下清理情况,我自己经常使用这种情况,而且这种情况非常常见,可能是try{} catch{}结构在许多语言中都不可用的原因。完成相同任务所需的检查和额外变量的数量远比完成跳转所需的一两个指令要糟糕得多,而且,额外的函数解决方案根本不是解决方案。你不能告诉我这更容易管理或更易于阅读。

现在,代码空间、堆栈使用和执行时间在许多情况下对许多程序员来说可能还不够重要,但当您在一个只有2KB代码空间的嵌入式环境中工作时,为了避免一个明确定义的“goto”而额外执行50字节的指令是可笑的,而且这种情况并不像许多高级程序员所认为的那样罕见。

“goto有害”这句话对迈向结构化编程非常有帮助,即使它总是一种过度概括。在这一点上,我们都听到了足够多的使用它的谨慎(我们应该)。当它显然是工作的正确工具时,我们不需要害怕它。

我只记得用过一次goto。我有一系列五个嵌套计数循环,我需要能够根据某些条件从内部打破整个结构:

    for{
      for{
        for{
          for{
            for{
              if(stuff){
                GOTO ENDOFLOOPS;
              }
            }
          }
        }
      }
    }
    
    ENDOFLOOPS:

我可以很容易地声明一个布尔中断变量,并将其用作每个循环的条件的一部分,但在这种情况下,我认为GOTO是一样实用和一样可读的。

没有迅猛龙攻击我。