298

每个人都知道 Dijkstra给编辑的信:go to 被认为有害的语句(也在这里.html 成绩单和这里.pdf),并且从那时起一直在大力推动尽可能避开 goto 语句。虽然可以使用 goto 生成无法维护的庞大代码,但它仍然存在于现代编程语言中。即使是 Scheme 中高级的延续控制结构,也可以说是复杂的 goto。

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

作为后续问题:C 提供了一对函数 setjmp() 和 longjmp(),它们不仅可以在当前堆栈帧内,而且可以在任何调用帧内进行跳转。这些是否应该被视为与 goto 一样危险?更危险?


Dijkstra 本人对这个头衔感到遗憾,他不对此负责。在EWD1308(也在这里.pdf)的结尾,他写道:

最后写一个短篇记录。1968 年,ACM 通讯发表了我的一篇题为“被认为有害的 goto 语句”的文本,在后来的几年里,它被最常引用,但遗憾的是,经常被那些只看过它的作者引用。标题,通过成为模板成为我成名的基石:对于几乎任何 X,我们都会看到标题为“X 被认为有害”的各种文章,包括标题为“Dijkstra 被认为有害”的文章。但是发生了什么?我已经提交了一篇题为“反对 goto 语句的案例”的论文”,为了加快出版速度,主编改成了“致主编的信”,并在此过程中给了它一个自己发明的新标题!主编是尼克劳斯·沃斯。

关于这个主题的一篇经过深思熟虑的经典论文,与 Dijkstra 的论文相匹配,是Donald E. Knuth的Structured Programming with go to Statements 。阅读都有助于重建上下文和对主题的非教条式理解。在这篇论文中,Dijkstra 对这个案例的看法被报道并且更加强烈:

Donald E. Knuth:我相信,通过提出这样的观点,我实际上并没有强烈反对 Dijkstra 的想法,因为他最近写道:“请不要陷入相信我对 [the go to statement]。我有一种不舒服的感觉,其他人正在把它变成一种宗教,好像编程的概念问题可以通过一个技巧,通过一种简单的编码纪律形式来解决!

4

49 回答 49

255

XKCD的GOTO漫画

我的一位同事说,使用 GOTO 的唯一原因是,如果您将自己编程到一个角落,那它是唯一的出路。换句话说,提前进行适当的设计,以后就不需要使用 GOTO。

我认为这部漫画很好地说明了“我可以重组程序的流程,或者改用一个小‘GOTO’。” 当您的设计薄弱时,GOTO 是一个薄弱的出路。 迅猛龙捕食弱者

于 2008-09-05T20:06:00.637 回答
191

以下陈述是概括性的;虽然总是可以请求例外,但通常(根据我的经验和拙见)不值得冒险。

  1. 不受限制地使用内存地址(GOTO 或原始指针)提供了太多机会来犯容易避免的错误。
  2. 到达代码中特定“位置”的方法越多,人们对系统在该点的状态就越没有信心。(见下文。)
  3. 结构化编程恕我直言,与其说是“避免 GOTO”,不如说是让代码的结构与数据的结构相匹配。例如,重复的数据结构(例如数组、顺序文件等)自然由重复的代码单元处理。具有内置结构(例如,while、for、until、for-each 等)允许程序员避免重复相同陈词滥调的代码模式的乏味。
  4. 即使 GOTO 是低级实现细节(并非总是如此!),它也低于程序员应该考虑的级别。有多少程序员在原始二进制中平衡他们的个人支票簿?有多少程序员担心磁盘上的哪个扇区包含特定记录,而不仅仅是为数据库引擎提供密钥(如果我们真的根据物理磁盘扇区编写所有程序,有多少种方法会出错)?

上述脚注:

关于第 2 点,请考虑以下代码:

a = b + 1
/* do something with a */

在代码中的“做某事”点,我们可以满怀信心地声明a大于b. (是的,我忽略了未捕获的整数溢出的可能性。我们不要陷入一个简单的例子。)

另一方面,如果代码是这样读的:

...
goto 10
...
a = b + 1
10: /* do something with a */
...
goto 10
...

获得标签 10 的方法多种多样,这意味着我们必须更加努力地工作,才能对那时a之间的关系充满信心。b(事实上​​,在一般情况下它是不可判定的!)

关于第 4 点,代码中“去某个地方”的整个概念只是一个比喻。除了电子和光子(用于废热)之外,CPU 内部的任何地方都没有真正“运行”。有时我们会放弃另一个更有用的隐喻。我记得遇到过(几十年前!)一种语言

if (some condition) {
  action-1
} else {
  action-2
}

通过将 action-1 和 action-2 编译为离线无参数例程,然后使用单个双参数 VM 操作码在虚拟机上实现,该操作码使用条件的布尔值来调用其中一个或另一个。这个概念只是“选择现在调用什么”而不是“去这里或去那里”。再次,只是比喻的变化。

于 2008-09-09T16:34:07.100 回答
139

有时在单个函数中使用 GOTO 作为异常处理的替代方法是有效的:

if (f() == false) goto err_cleanup;
if (g() == false) goto err_cleanup;
if (h() == false) goto err_cleanup;

return;

err_cleanup:
...

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

于 2008-09-05T19:17:41.267 回答
130

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

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

ENDOFLOOPS:

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

没有迅猛龙攻击我。

于 2008-09-06T13:56:28.113 回答
96

Goto 在我的程序列表中非常低,只是为了它。这并不意味着它是不可接受的。

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

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

在极少数情况下,当您的算法通过一系列节点(状态)的路径通过一组有限的允许转换(goto)而不是通过任何更具体的控制流(循环、条件、诸如此类)连接的路径方面确实是最容易理解的),那么这应该在代码中明确。你应该画一个漂亮的图表。

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

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

从来没有,也永远不会有任何一种语言,用它来编写糟糕的代码是最困难的。——唐纳德·克努斯。

从 C 中取出 goto 不会使用 C 编写好的代码变得更容易。事实上,它宁愿错过 C应该能够充当一种美化的汇编语言这一点。

接下来是“被认为有害的指针”,然后是“被认为有害的鸭子打字”。那么当他们来拿走你不安全的编程结构时,谁会为你辩护呢?诶?

于 2008-09-23T04:17:34.997 回答
95

我们已经讨论过了,我坚持我的观点

此外,我受够了人们将高级语言结构描述为“变相的<code>goto”,因为他们显然根本不明白这一点。例如:

即使是 Scheme 中高级的延续控制结构,也可以说是复杂的 goto。

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

同样,说“GOTO 是一种工具,并且作为所有工具,它可以被使用和滥用”是完全不合时宜的。没有现代建筑工人会使用岩石并声称它“是一种工具”。岩石已被锤子取代。goto已被控制结构取代。如果建筑工人没有锤子被困在野外,他当然会用石头代替。如果程序员必须使用没有特性 X 的劣质编程语言,那么她当然可能不得不goto改用。但是,如果她在其他任何地方使用它而不是适当的语言功能,她显然没有正确理解该语言并错误地使用它。真的就这么简单。

于 2008-09-06T14:00:07.777 回答
70

In Linux: Using goto In Kernel Code on Kernel Trap,与 Linus Torvalds 和一位“新人”讨论了在 Linux 代码中使用 GOTO。那里有一些非常好的观点,Linus 穿着惯常的傲慢:)

一些段落:

Linus:“不,你被 CS 的人洗脑了,他们认为 Niklaus Wirth 真的知道他在说什么。他不知道。他没有一个可怕的线索。”

-

Linus:“我认为 goto 很好,而且它们通常比大量缩进更具可读性。”

-

Linus:“当然,在像 Pascal 这样的愚蠢语言中,标签不能是描述性的,goto 可能很糟糕。”

于 2008-09-06T13:37:40.810 回答
51

在 C 中,goto只能在当前函数的范围内工作,这往往会定位任何潜在的错误。setjmp并且longjmp更加危险,是非本地的,复杂的和依赖于实现的。然而,在实践中,它们太模糊和不常见,不会引起很多问题。

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

3420 IF A > 2 THEN GOTO 1430

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

于 2008-09-06T14:50:27.517 回答
51

今天,很难看出这GOTO句话有什么大不了的,因为“结构化编程”的人大多赢得了辩论,而今天的语言有足够的控制流结构可以避免GOTO

goto计算现代 C 程序中 s 的数量。现在添加breakcontinuereturn语句的数量。此外,添加您使用ifelse、或的次数。如果您在 1968 年 Dijkstra 写信时使用 FORTRAN 或 BASIC 编写程序,那么您的程序将有多少s。whileswitchcaseGOTO

当时的编程语言缺乏控制流。例如,在原始的达特茅斯 BASIC 中:

  • IF声明没有ELSE。如果你想要一个,你必须写:

    100 IF NOT condition THEN GOTO 200
    ...stuff to do if condition is true...
    190 GOTO 300
    200 REM else
    ...stuff to do if condition is false...
    300 REM end if
    
  • 即使您的IF语句不需要ELSE,它仍然仅限于单行,通常由GOTO.

  • 没有任何DO...LOOP声明。对于非FOR循环,您必须以明确的方式结束循环GOTOIF...GOTO回到开头。

  • 没有SELECT CASE。你必须使用ON...GOTO.

所以,你最终在你的程序中有很多sGOTO。而且您不能将GOTOs 限制在单个子例程中(因为子例程GOSUB...RETURN的概念如此弱),因此这些GOTOs 可以去任何地方。显然,这使得控制流难以遵循。

这就是反GOTO运动的由来。

于 2010-03-06T23:49:11.127 回答
35

在某些情况下,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 竭尽全力避免使用 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 而使用了一些非常愚蠢的东西,那么不要这样做。

于 2009-04-12T09:32:38.537 回答
30

Donald E. Knuth 在 1992 CSLI 的“Literate Programming”一书中回答了这个问题。在页。17 有一篇文章“ Structured Programming with goto Statements ”(PDF)。我认为这篇文章可能也已在其他书籍中发表过。

这篇文章描述了 Dijkstra 的建议,并描述了这种建议有效的情况。但他也给出了一些仅使用结构化循环无法轻松重现的反例(问题和算法)。

文章包含问题的完整描述、历史、例子和反例。

于 2008-09-06T13:47:18.620 回答
28

Goto 认为很有帮助。

我在 1975 年开始编程。对于 1970 年代的程序员来说,“goto 被认为有害”这个词或多或少地表明具有现代控制结构的新编程语言值得尝试。我们确实尝试了新语言。我们很快就转换了。我们再也没有回去过。

我们再也没有回去过,但是,如果你更年轻,那么你一开始就没有去过那里。

现在,除了作为程序员年龄的指标外,古代编程语言的背景可能不是很有用。尽管如此,年轻的程序员缺乏这种背景,所以他们不再理解“goto 认为有害”口号在引入时传达给目标受众的信息。

一个不理解的口号不是很有启发性。最好忘记这些口号。这样的口号无济于事。

然而,“Goto 被认为是有害的”这个特殊的口号已经拥有了自己的不死生命。

goto不能被滥用吗?答:当然可以,但那又怎样?实际上,每个编程元素都可能被滥用。例如,谦虚bool的人受到的虐待比我们中的一些人愿意相信的要多。

相比之下,我不记得自 1990 年以来遇到过一个实际的 goto 滥用实例。

goto 最大的问题可能不是技术问题,而是社会问题。不太了解的程序员有时似乎觉得弃用 goto 会让他们听起来很聪明。你可能不得不时不时地满足这样的程序员。这就是人生。

goto 今天最糟糕的地方是它没有被充分使用。

于 2014-09-10T19:26:19.190 回答
27

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

我没有看到讨论过的一件事是这种代码,虽然不是很常见,但在 Fortran 教科书中讲授过。诸如 DO 循环和开放编码子例程的扩展范围之类的东西(请记住,这将是 Fortran II、Fortran IV 或 Fortran 66,而不是 Fortran 77 或 90)。至少有可能语法细节不准确,但概念应该足够准确。每种情况下的片段都在一个函数中。

请注意,Kernighan & Plauger 撰写的优秀但过时(且已绝版)的书“ The Elements of Programming Style, 2nd Edn ”包含了一些在其时代(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

这种胡说八道的原因之一是老式的打孔卡。您可能会注意到标签(很乱,因为那是规范样式!)在第 1 列(实际上,它们必须在第 1-5 列),代码在第 7-72 列(第 6 列是延续标记列)。第 73-80 列将被赋予一个序列号,并且有些机器会将穿孔卡片组按序列号顺序排序。如果您的程序在序列卡上,并且需要在循环中间添加几张卡(行),那么您必须在这些额外行之后重新处理所有内容。然而,如果你用 GOTO 的东西替换了一张卡,你可以避免重新排序所有的卡——你只是在例程结束时用新的序列号塞进新卡。认为这是“绿色计算”的第一次尝试

哦,您可能还注意到我是在作弊而不是大喊大叫——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。谷歌搜索分配的 goto告诉我它已从 Fortran 95 中删除。为结构化编程革命加油,可以说它是从 Dijkstra 的“GOTO 被认为是有害的”信件或文章公开开始的。

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

于 2009-02-14T08:39:57.247 回答
19

没有像GOTO 这样的东西被认为是有害的。

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

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

就我个人而言,我不会在典型代码中使用任何一种,但我对GOTOWITH的奇怪用法是有保证的,并且替代解决方案将包含更多代码。

最好的解决方案是让编译器只警告您关键字被污染,并且您必须在语句周围填充几个 pragma 指令以消除警告。

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

于 2008-09-05T19:17:12.610 回答
17

自从我开始在 linux 内核中做一些事情以来,goto 就不再像以前那样困扰我了。起初,看到他们(内核人员)在我的代码中添加了 goto,我有点害怕。从那以后,我已经习惯了在某些有限的情况下使用 goto,现在我自己也会偶尔使用它们。通常,它是跳转到函数末尾以进行某种清理和救助的 goto,而不是在函数的多个位置复制相同的清理和救助。通常,它不足以传递给另一个函数——例如,释放一些本地 (k)malloc'ed 变量是一种典型情况。

我编写的代码只使用了一次 setjmp/longjmp。它在一个 MIDI 鼓音序器程序中。播放发生在与所有用户交互不同的进程中,并且播放进程使用与 UI 进程共享的内存来获取执行播放所需的有限信息。当用户想要停止播放时,播放过程只是做了一个 longjmp “回到开头”来重新开始,而不是在用户想要它停止时对它正在执行的地方进行一些复杂的展开。它工作得很好,很简单,在那种情况下我从来没有遇到过任何与之相关的问题或错误。

setjmp/longjmp 有它们的位置——但那个地方是你不太可能去的地方,但很长一段时间才会去一次。

编辑:我只是看了代码。我使用的实际上是 siglongjmp(),而不是 longjmp(不是说这很重要,但我忘记了 siglongjmp 甚至存在。)

于 2009-04-23T03:53:02.520 回答
16

只要您能够为自己思考,就永远不会。

于 2009-04-12T08:17:54.147 回答
16

因为goto可用于混淆元编程

Goto既是高级控制表达式又是低级控制表达式,因此它没有适合大多数问题的适当设计模式。

从某种意义上说,它是低级的,goto 是一种实现更高级别的操作的原始操作,例如whileor foreach

从某种意义上说,它是高级别的,当以某种方式使用时,它需要以清晰的顺序以不间断的方式执行的代码,结构化循环除外,并将其更改为具有足够gotos的逻辑片段-动态重组的逻辑包。

所以,有平淡无奇邪恶的一面goto

平淡无奇的一面是向上指向的 goto 可以实现完全合理的循环,向下指向的 goto 可以执行完全合理的breakor return。当然,实际的while, break, orreturn会更具可读性,因为可怜的人不必goto为了获得大局而模拟 的效果。所以,一般来说是个坏主意。

邪恶的一面涉及一个例程,它不使用 goto 来处理 while、break 或 return,而是将其用于所谓的意大利面条逻辑。在这种情况下,喜欢 goto 的开发人员正在从 goto 的迷宫中构建代码片段,而理解它的唯一方法是在心理上将其作为一个整体进行模拟,当 goto 有很多时,这是一项非常累人的任务。我的意思是,想象一下评估代码的麻烦,其中 selse不完全是 s 的倒数if,其中嵌套if的 s 可能允许某些被外部拒绝的东西if等等,等等。

最后,为了真正涵盖这个主题,我们应该注意到,除了 Algol 之外,基本上所有早期语言最初都只做了单一陈述,受其版本的if-then-else. 因此,执行条件块的唯一方法是goto使用逆条件来绕过它。疯了,我知道,但我读过一些旧的规格。请记住,第一台计算机是用二进制机器代码编程的,所以我认为任何类型的 HLL 都是救命稻草;我猜他们对他们获得的 HLL 功能并不太挑剔。

说了这么多,我曾经在goto我写的每个程序中都加入一个“只是为了惹恼纯粹主义者”

于 2009-12-07T05:09:37.713 回答
15

如果你正在用 C 编写一个 VM,结果证明使用 (gcc's) 计算的 goto 是这样的:

char run(char *pc) {
    void *opcodes[3] = {&&op_inc, &&op_lda_direct, &&op_hlt};
    #define NEXT_INSTR(stride) goto *(opcodes[*(pc += stride)])
    NEXT_INSTR(0);
    op_inc:
    ++acc;
    NEXT_INSTR(1);
    op_lda_direct:
    acc = ram[++pc];
    NEXT_INSTR(1);
    op_hlt:
    return acc;
}

比循环内的传统开关快得多。

于 2008-09-06T15:53:17.193 回答
11

拒绝程序员使用 GOTO 语句就像告诉木匠不要使用锤子,因为它可能会在他敲钉子时损坏墙壁。真正的程序员知道如何以及何时使用 GOTO。我跟踪了其中一些所谓的“结构化程序”,我看到这些可怕的代码只是为了避免使用 GOTO,我可以射杀程序员。好吧,为了保护对方,我也一次又一次地看到了一些真正的意大利面条代码,那些程序员也应该被枪杀。

这只是我发现的一个小代码示例。

  YORN = ''
  LOOP
  UNTIL YORN = 'Y' OR YORN = 'N' DO
     CRT 'Is this correct? (Y/N) : ':
     INPUT YORN
  REPEAT
  IF YORN = 'N' THEN
     CRT 'Aborted!'
     STOP
  END

- - - - - - - - - - - -或者 - - - - - - - - - - -

10:  CRT 'Is this Correct (Y)es/(N)o ':

     INPUT YORN

     IF YORN='N' THEN
        CRT 'Aborted!'
        STOP
     ENDIF
     IF YORN<>'Y' THEN GOTO 10
于 2008-12-10T18:12:16.487 回答
9

“在这个链接http://kerneltrap.org/node/553/2131

具有讽刺意味的是,消除 goto 引入了一个错误:省略了 spinlock 调用。

于 2009-02-14T07:52:00.870 回答
7

原始论文应该被认为是“无条件 GOTO 被认为是有害的”。它特别提倡一种基于条件 ( if) 和迭代 ( while) 结构的编程形式,而不是早期代码常见的测试和跳转。goto在不存在适当控制结构的某些语言或情况下仍然有用。

于 2008-09-05T19:08:25.190 回答
7

关于我同意 Goto可以使用的唯一地方是当您需要处理错误时,并且发生错误的每个特定点都需要特殊处理。

例如,如果您正在获取资源并使用信号量或互斥锁,则必须按顺序获取它们,并且应该始终以相反的方式释放它们。

一些代码需要一种非常奇怪的模式来获取这些资源,并且您不能仅仅编写一个易于维护和理解的控制结构来正确处理这些资源的获取和释放以避免死锁。

在没有 goto 的情况下总是可以正确完成,但在这种情况下和其他一些情况下,Goto 实际上是更好的解决方案,主要是为了可读性和可维护性。

-亚当

于 2008-09-05T19:45:19.140 回答
5

一种现代 GOTO 用法是由 C# 编译器为由 yield return 定义的枚举器创建状态机。

GOTO 是编译器而不是程序员应该使用的东西。

于 2008-09-05T19:20:45.317 回答
5

在 C 和 C++(以及其他罪魁祸首)标记中断并继续之前,goto 将继续发挥作用。

于 2008-09-06T14:44:38.230 回答
5

如果 GOTO 本身是邪恶的,那么编译器就是邪恶的,因为它们会生成 JMP。如果跳转到一个代码块,尤其是跟随一个指针,本质上是邪恶的,那么 RETurn 指令就是邪恶的。相反,邪恶在于滥用的可能性。

有时我不得不编写必须跟踪多个对象的应用程序,其中每个对象必须遵循复杂的状态序列以响应事件,但整个事情绝对是单线程的。如果以伪代码表示,典型的状态序列将是:

request something
wait for it to be done
while some condition
    request something
    wait for it
    if one response
        while another condition
            request something
            wait for it
            do something
        endwhile
        request one more thing
        wait for it
    else if some other response
        ... some other similar sequence ...
    ... etc, etc.
endwhile

我确信这不是新的,但我在 C(++) 中处理它的方式是定义一些宏:

#define WAIT(n) do{state=(n); enque(this); return; L##n:;}while(0)
#define DONE state = -1

#define DISPATCH0 if state < 0) return;
#define DISPATCH1 if(state==1) goto L1; DISPATCH0
#define DISPATCH2 if(state==2) goto L2; DISPATCH1
#define DISPATCH3 if(state==3) goto L3; DISPATCH2
#define DISPATCH4 if(state==4) goto L4; DISPATCH3
... as needed ...

然后(假设状态最初为0)上面的结构化状态机变成结构化代码:

{
    DISPATCH4; // or as high a number as needed
    request something;
    WAIT(1); // each WAIT has a different number
    while (some condition){
        request something;
        WAIT(2);
        if (one response){
            while (another condition){
                request something;
                WAIT(3);
                do something;
            }
            request one more thing;
            WAIT(4);
        }
        else if (some other response){
            ... some other similar sequence ...
        }
        ... etc, etc.
    }
    DONE;
}

对此有所不同,可以有 CALL 和 RETURN,因此某些状态机可以像其他状态机的子程序一样工作。

这不寻常吗?是的。维护者需要一些学习吗?是的。这种学习有回报吗?我认同。如果没有跳转到块中的 GOTO,它可以完成吗?没有。

于 2008-11-27T13:49:26.850 回答
5

实际上,我发现自己被迫使用 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 在这里是合理的。

雨果

于 2009-01-17T18:55:14.780 回答
5

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

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

现在考虑一下我自己经常使用的清理案例,它很常见,以至于可能是许多语言中不可用的 try{} catch {} 结构的原因。完成同一件事所需的检查和额外变量的数量远比进行跳转的一两条指令差得多,而且,附加功能解决方案根本不是解决方案。你不能告诉我这更易于管理或更易读。

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

“goto 是有害的”这句话对于转向结构化编程非常有帮助,即使它总是过度概括。在这一点上,我们都已经听够了,对使用它持谨慎态度(我们应该这样做)。当它显然是适合这项工作的工具时,我们不需要害怕它。

于 2012-06-06T20:21:09.337 回答
4

我避免使用它,因为同事/经理无疑会在代码审查或偶然发现它时质疑它的使用。虽然我认为它有用途(例如错误处理案例) - 你会与其他一些会遇到某种问题的开发人员发生冲突。

这不值得。

于 2008-09-05T20:16:46.040 回答
4

几乎所有可以使用 goto 的情况,您都可以使用其他结构来做同样的事情。无论如何,编译器都会使用 Goto。

我个人从不明确使用它,也不需要。

于 2010-06-17T07:41:44.683 回答
3

你可以用它来打破深度嵌套的循环,但大多数时候你的代码可以被重构为更干净,而没有深度嵌套的循环。

于 2008-09-05T19:08:31.693 回答
3

GOTO 就像台锯,在采取适当的安全措施时非常有用。

我认为它有害,因为大多数初学者在使用台锯和 GOTO 时都会失去手指。

在某些情况下,它是控制流量的唯一方法,但可以避免这些情况。

于 2008-09-05T19:22:25.403 回答
3

是的,GOTO 仍然被认为是有害的。当您发现自己处于使用 GOTO 可能有效的罕见情况时,您应该对自己的编程技能有足够的信心,不需要其他人的验证。任何允许您在范围内跳得比 GOTO 允许的更远的 GOTO 类函数都应该被认为比 GOTO 更危险。

于 2009-04-23T03:37:27.643 回答
3

C++ 包含构造函数和析构函数。这允许称为 RAII 的模式(资源分配是初始化)。基本上,您创建一个本地堆栈变量,创建堆栈变量的行为会打开一个文件、分配内存、锁定互斥体或以其他方式获取稍后必须释放的资源。

当变量超出范围时,析构函数运行并释放资源。

C没有这个功能。但是你仍然经常需要在函数开始时获取资源,并在结束时释放它们。

您的函数可能有一个或多个错误条件导致它提前返回。您不想复制资源释放代码。解决方案是使用 goto。

例子:

int
foo(const char *arg)
{
    char *argcopy = strdup(arg);

    if (!isvalid(argcopy))
        goto out1;

    FILE *myfile = fopen(argcopy, "r");
    if (myfile == NULL)
      goto out1;

    char bytes[10];
    if (fread(bytes, sizeof(bytes), 1, myfile) != sizeof(mybytes))
        goto out2;

    /* do some actual work */
    /* .... */
    /* end of actual work */

    out2:
    fclose(myfile);

    out1:
    free(argcopy);

    return 0;
 }
于 2012-06-02T04:08:06.860 回答
2

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

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

要回答您的问题,goto 仍然被认为有害的人认为是有害的。Goto 很容易失去结构化编程的优势。

最后,这是你的程序;因此你的决定。我建议在您能够自己回答问题之前不要使用 goto,但要在特定问题的背景下使用。

于 2008-09-23T18:29:12.797 回答
2

虽然我认为在几乎任何情况下最好避免 goto,但也有例外。例如,与其他更复杂的方法相比,我见过 goto 语句是优雅的解决方案的一个地方是为解释器实现尾调用消除。

于 2009-02-14T20:40:59.400 回答
1

我只在 Basic(即 VB、VBScript 等)和批处理文件中需要它。然后我只用它来处理错误。在 Basic 中,我倾向于只使用“on error goto”。在批处理文件中我必须使用它,因为没有 else 命令。然后,我只将它们用作指向有意义标签的前向跳转。

于 2008-09-05T20:42:52.420 回答
1

使用 goto 使得编写不是特别可维护的“意大利面条代码”变得太容易了。要遵循的最重要的规则是编写可读的代码,但这当然取决于项目的目标是什么。作为“最佳实践”,避免 goto 是一个好主意。这是极端编程类型称为“代码气味”的东西,因为它表明您可能做错了什么。在循环中使用 break 与 goto 非常相似,只是它不是 goto,但这再次表明代码可能不是最优的。这就是为什么,我相信,不要找到更多现代编程漏洞也很重要,这些漏洞本质上是一个不同名称的 goto。

于 2008-09-09T17:02:00.450 回答
1

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

这是一个可怕的结块,有多个严重的问题,最严重的是在它下面的所有函数都终止之前,任何函数都不能终止。

但它开发得很快,在它旨在解决的有限问题上运行良好,并且明确地展示了程序的逻辑和流程,当我对其进行重构和扩展以包含在另一个项目中时,它运行良好。

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

于 2008-11-28T03:04:21.817 回答
1

许多现代编程语言使用它们的编译器来强制限制 GOTO 的使用——这减少了潜在的风险。例如,C# 不允许您使用 GOTO 从外部跳转到循环体。文档中提到了限制

这是 GOTO 有时比以前更安全的一个例子。

在某些情况下,使用 GOTO 与从函数中提前返回相同(即提前跳出循环)。然而,可以争论好的形式。

于 2009-10-24T03:54:25.797 回答
1

在我看来,“goto 有害”更多的是关于封装和状态的一致性,而不是其他任何东西。

许多代码,甚至是“oo”代码,都像任何意大利面条代码一样具有糟糕的混乱状态封装。

“goto 被认为有害”的问题在于,它给只查看机械规则的程序员留下了这样一种印象,即唯一可用的流控制是返回方法,并且很容易导致传递很多状态通过引用 -导致缺乏状态封装,'goto 认为有害' 试图摆脱的东西。

遵循典型的“OO”代码库中的控制流,并告诉我我们还没有意大利面条代码....(顺便说一句,我不是指通常会被讨厌的“馄饨”代码-馄饨代码的执行路径通常非常简单,即使对象关系不是很明显)。

或者,换一种说法,只有当每个子例程只修改局部状态时,避免 goto 支持一切都是子例程才有用,只有通过该子例程(或至少该对象)才能修改局部状态。

于 2010-03-06T23:56:34.657 回答
1

goto 存在一些问题。一是很难看出代码是如何流动的。由于花括号,更容易看到 if 块,但 goto 对您隐藏了它。此外,while 和 if 本质上也是 goto,但它们有助于解释为什么您在代码中来回跳跃。使用常规 goto,您必须自己拼凑。

作为练习,尝试编写一些代码来计算斐波那契数列,看看完成后阅读的难度。

如果您要处理该代码,那么我建议您编写一些单元测试并重写它。否则,就顺其自然吧。

综上所述,有时出于性能原因,“可能”适合使用 goto。

于 2010-08-09T20:53:18.410 回答
1

并不是 goto 本身不好。就是当相同的逻辑可以用另一种方式更清楚地表达时使用 goto 是不好的。它可以使代码非常难以遵循并且使维护变得困难。只需以 Bad Old Days 中的 Basic 程序为例。

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

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

goto 还有另一个问题,这纯粹是政治问题:很多人讨厌它,并且在代码中使用它,即使是有道理的,也可能会导致问题。如果这是分配代码,那么是的,重写它,否则你可能会被标记下来。否则,我倾向于将其保留到下次您需要对该部分进行维护时。

于 2010-08-09T21:08:43.230 回答
1

goto可能很有用,这里是一个非常强大的开源国际象棋引擎stockfish的例子c++,用. 只是跳过了一些条件检查(效率增益),如果不是该语句goto,程序必须执行这些条件检查。goto如果goto语句标签在goto声明之后,那么它们是非常无害且可读的。

于 2014-06-29T04:49:09.277 回答
0

基本思想是 goto 给了你太多的自由去做你不打算做的事情。它可能会在看起来与 goto 语句无关的地方导致错误,从而使代码维护更加困难。如果你认为你需要一个 goto 语句,那你就错了 :) 你应该重新考虑你的代码构造。这就是为什么现代编程语言付出了很多努力来为您提供可读、可维护的流控制结构和异常处理机制。

我也将不同意lassevk。由于 goto 被滥用而不是正确使用,我相信它在设计良好的语言中没有地位。即使对于 goto 的“理想”用途,也应该首选需要更多代码的其他方法。

所以总而言之,是的,它仍然被认为是有害的。

于 2008-09-05T19:23:14.343 回答
0

这个,这是 GoTo 的一个很好的用法,但是在带有垃圾收集器的语言中,我认为使用 GoTo 的唯一原因是混淆你的代码(混淆器工具使用 GoTo 来隐藏他们的代码)

于 2009-04-12T08:10:23.680 回答
0

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

对于错误和共同线程,我认为 setcontex 或 setjmp(如果可用)“更好”。

于 2009-04-23T03:16:38.597 回答
0

在生成 C 状态机时使用 GOTO 会很好。我永远不会在手写代码中使用 GOTO ——“现代”语言结构使它完全没有必要。

setjmp/longjmp 结构在某些情况下可能很有用(当缺少“真正的”异常时,或者当您正在实现像鸡方案这样的东西时),但它在“普通”编程中没有位置。

于 2009-05-18T22:40:40.063 回答
0

在一个完美的世界里,我们永远不需要 GOTO。然而,我们生活在一个不完美的世界。我们没有拥有我们可以梦想的所有控制结构的编译器。有时我觉得使用 GOTO 比拼凑一个实际上并不存在的控制结构更好。

最常见的(不是很常见)是循环半结构。您总是执行第一部分,也许您执行其余部分然后返回并再次执行第一部分。当然,您可以在 while 循环中使用布尔标志来实现它,但我不喜欢这个答案,因为我认为它不太清楚。当你看到类似的东西时:

loop:
  GetSomeData;
  if GotData then
     Begin
        ProcessTheData;
        StoreTheResult;
        Goto Loop;
     End;

对我来说这比

Repeat
  GetSomeData;
  Flag := GotData;
  if Flag then
    Begin
      ProcessTheData;
      StoreTheResult;
    End;
Until Not Flag;

有时

Function GotTheData;

Begin
  GetSomeData;
  Result := GotData;
End;

While GotTheData do
  Begin
    ProcessTheData;
    StoreTheResult;
  End;

不是一个可行的答案,我坚信代码应该是清晰的。如果我必须发表评论来解释代码在做什么,我会考虑是否可以使代码更清晰并摆脱评论。

于 2009-10-24T03:46:26.930 回答
0

Java String类源码中跳转示例:

int firstUpper;

/* Now check if there are any characters that need to be changed. */
scan: {
    for (firstUpper = 0 ; firstUpper < count; ) {
         char c = value[offset+firstUpper];
         if ((c >= Character.MIN_HIGH_SURROGATE) &&
                 (c <= Character.MAX_HIGH_SURROGATE)) {
             int supplChar = codePointAt(firstUpper);
             if (supplChar != Character.toLowerCase(supplChar)) {
                  break scan;
             }
             firstUpper += Character.charCount(supplChar);
         } else {
             if (c != Character.toLowerCase(c)) {
                  break scan;
             }
             firstUpper++;
         }
     }
     return this;
}
[... subsequent use of firstUpper ...]

这可以用很少的开销重写,例如:

 int firstUpper = indexOfFirstUpper();
 if (firstUpper < 0) return this; 

即使在现代语言中,即使我实际上并不喜欢使用 goto,但我认为在许多情况下这些是可以接受的,在像这样的低级情况下,我看起来更好(而且它不仅仅是退出循环)。

无意重振宗教战争。

于 2013-02-19T15:13:46.210 回答