GOTO仍被视为有害吗? [关闭]
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了GOTO仍被视为有害吗? [关闭]相关的知识,希望对你有一定的参考价值。
每个人都知道Dijkstra的Letters to the editor: go to statement considered harmful(也是here .html transcript和here .pdf)并且从那时起就有一个强大的推动力,只要有可能就避开goto语句。虽然可以使用goto来生成不可维护的庞大代码,但它仍然存在于modern programming languages中。甚至Scheme中的高级continuation控制结构也可以被描述为复杂的goto。
什么情况下可以使用goto?什么时候最好避免?
作为后续问题:C提供了一对函数setjmp和longjmp,它们不仅可以在当前堆栈帧内,而且可以在任何调用帧中进行转换。这些应该被视为像goto一样危险吗?更危险吗?
Dijkstra本人对这个头衔表示遗憾,他对此不负责任。在EWD1308(也是here .pdf)结束时,他写道:
最后是一个短篇小说的记录。 1968年,ACM的通讯以“被认为有害的goto声明”的标题发表了我的一篇文章,遗憾的是,在后来的几年里,这一期刊最常被引用,但是,经常被作者看到的不多于标题,通过成为一个模板成为我的名声的基石:我们会看到几乎任何X的标题“X被认为有害”的各种文章,包括一个标题为“Dijkstra被认为有害”的文章。但是发生了什么?我提交了一篇题为“反对goto声明的案例”的论文,为了加快其出版速度,编辑已经变成了“致编辑的信”,并在此过程中给了它一个新的他自己发明的头衔!编辑是Niklaus Wirth。
关于这个主题的经过深思熟虑的经典论文,与Dijkstra相匹配的是由Donald E. Knuth撰写的Structured Programming with go to Statements。阅读都有助于重新建立背景和对主题的非教条性理解。在本文中,Dijkstra对此案的观点得到了报道,甚至更为强烈:
Donald E. Knuth:我相信通过提出这样一种观点,我实际上并不同意Dijkstra的观点,因为他最近写了以下内容:“请不要陷入相信我非常悲惨的陷阱。我有一种令人不舒服的感觉,就是其他人正在创造一种宗教信仰,好像编程的概念问题可以通过一个简单的编码规则来解决!
以下陈述是概括;虽然总是可以恳求例外,但通常(根据我的经验和拙见)并不值得冒这个风险。
- 无限制地使用内存地址(GOTO或原始指针)提供了太多机会来轻松避免错误。
- 到达代码中特定“位置”的方式越多,对该系统状态的信心就越不自信。 (见下文。)
- 结构化编程IMHO不是关于“避免GOTO”,而是关于使代码结构与数据结构相匹配。例如,重复数据结构(例如,阵列,顺序文件等)由重复的代码单元自然地处理。具有内置结构(例如,while,for,until,for-each等)允许程序员避免重复相同的陈词滥调代码模式的乏味。
- 即使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编译为out-of-line无参数例程,然后使用单个双参数VM操作码,使用条件的布尔值来调用其中一个,从而在虚拟机上实现。这个概念只是“选择现在要调用的东西”,而不是“去这里或去那里”。再一次,只是一个隐喻的变化。
在某些情况下,Go To可以为“真正的”异常处理提供一种替代。考虑:
GOTO
显然,这段代码被简化为占用更少的空间,所以不要太过于挂在细节上。但是考虑一下我在生产代码中看到过多次的替代方法,编码人员为了避免使用goto而荒谬的长度:
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;
从功能上讲,这段代码完全相同。实际上,编译器生成的代码几乎完全相同。然而,在程序员热衷于安抚Nogoto(可怕的学术谴责之神)的热情中,这位程序员完全打破了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);
循环所代表的基本习惯,并对代码的可读性做了一个真实的数字。这不是更好。
所以,故事的寓意是,如果你发现自己为了避免使用goto而采取真正愚蠢的事情,那就不要了。
Donald E. Knuth在1992年的CSLI“Literate Programming”一书中回答了这个问题。在p。 17有一篇文章“while
”(PDF)。我认为这篇文章也可能已在其他书籍中发表过。
这篇文章描述了Dijkstra的建议,并描述了这种情况的有效性。但他也提供了许多反例(问题和算法),这些例子只能使用结构化循环来轻松复制。
本文包含问题的完整描述,历史,示例和反例。
被Jay Ballou吸引并添加一个答案,我将加上0.02英镑。如果Bruno Ranschaert还没有这样做,我会提到Knuth的“使用GOTO语句进行结构化编程”一文。
我没有看过的一件事就是在Fortran教科书中讲授的一些代码,虽然不常见,但却很常见。像DO循环和开放编码子程序的扩展范围(记住,这将是Fortran II,Fortran IV或Fortran 66 - 而不是Fortran 77或90)。语法细节至少有可能是不精确的,但概念应该足够准确。每种情况下的片段都在一个函数内。
请注意,Kernighan&Plauger撰写的优秀但过时(绝版)的书籍“Structured Programming with goto Statements”包含了一些现实生活中的例子,这些例子表明GOTO在其时代(70年代后期)的编程教科书中被滥用。但是,下面的材料不是那本书。
Extended range for a DO loop
The Elements of Programming Style, 2nd Edn
这种废话的一个原因是好老式的打卡。您可能会注意到标签(很好地不按顺序,因为这是规范样式!)在第1列(实际上,它们必须在第1-5列中),代码在第7-72列中(第6列是延续标记栏)。第73-80列将被赋予序列号,并且有机器将打卡机卡片分类为序列号顺序。如果您的程序在顺序卡上并且需要在循环中间添加一些卡(行),则必须在这些额外行之后重新启动所有内容。但是,如果你用GOTO的东西替换了一张卡片,你可以避免重新测序所有的卡片 - 你只需要在例程结束时用新的序列号将新卡片塞进去。将其视为“绿色计算”的第一次尝试 - 节省打卡(或者更具体地说,节省重新输入劳动力 - 并节省相应的重新加密错误)。
哦,你可能还会注意到我在欺骗而不是大喊大叫 - Fortran IV通常用大写字母写成。
Open-coded subroutine
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
标签76和54之间的GOTO是计算goto的版本。如果变量i的值为1,则转到列表中的第一个标签(123);如果它的值为2,则转到第二个,依此类推。从76到计算goto的片段是开放编码的子例程。它是一段执行的代码,就像一个子程序,但写在一个函数体中。 (Fortran还具有语句功能 - 这些功能是嵌入在单行上的功能。)
有比构成的goto更糟糕的构造 - 你可以为变量分配标签,然后使用指定的goto。谷歌搜索 ...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
告诉我,它已从Fortran 95中删除了。用一个结构化编程革命的粉笔,可以说是公开的Dijkstra的“GOTO Considered Harmful”字母或文章。
如果不了解Fortran所做的各种事情(以及其他语言,其中大部分已被正确地撇开),我们新手很难理解Dijkstra正在处理的问题的范围。哎呀,直到那封信发表十年后我才开始编程(但我确实不幸在Fortran IV中编程了一段时间)。
转到认为有帮助。
我在1975年开始编程。对于20世纪70年代的程序员来说,“转向被认为是有害的”这些词或多或少地表示具有现代控制结构的新编程语言值得尝试。我们确实尝试过新语言。我们很快转换了。我们再也没有回去过。
我们再也没有回去,但是,如果你年轻,那么你从来没有去过那里。
现在,除了作为程序员年龄的指标之外,古代编程语言的背景可能不是很有用。尽管如此,年轻的程序员缺乏这种背景,因此他们不再理解在引入时向其目标受众传达的“转向被认为有害”的口号。
一个人不理解的标语并不是很有启发性。最好忘记这样的口号。这样的口号没有帮助。
然而,这个特殊的口号“Goto被认为是有害的”已经成为了自己的不死生命。
可以转到不被滥用?答:当然可以,但那又怎样?实际上每个编程元素都可能被滥用。例如,卑微的assigned goto比我们想要相信的人更容易受到虐待。
相比之下,我不记得自1990年以来遇到一个单一的,实际的goto滥用实例。
goto最大的问题可能不是技术问题,而是社交问题。有时候不太了解的程序员似乎觉得弃用goto会让他们听起来很聪明。您可能不得不满足这些程序员。这就是人生。
关于goto今天最糟糕的事情是它使用不够。
没有GOTO认为有害的东西。
GOTO是一种工具,作为所有工具,它可以被使用和滥用。
然而,编程世界中有许多工具比使用更容易被滥用,而GOTO就是其中之一。 Delphi的WITH语句是另一个。
就个人而言,我不会在典型代码中使用任何一种,但我已经保证了GOTO和WITH的奇怪用法,并且替代解决方案将包含更多代码。
最好的解决方案是编译器只是警告你关键字被污染了,你必须在语句周围填写一些pragma指令来摆脱警告。
这就像告诉你的孩子不要用剪刀跑。剪刀也不错,但使用它们可能不是保持健康的最佳方法。
自从我开始在linux内核中做了一些事情以来,getos并没有像以前那样困扰我。起初我有点害怕看到他们(内核人员)在我的代码中添加了getos。我已经习惯于在某些有限的环境中使用gotos,现在偶尔也会使用它们。通常,它是一个转到函数末尾进行某种清理和纾困的goto,而不是在函数中的几个位置复制相同的清理和挽救。通常情况下,它不足以传递给另一个功能 - 例如释放一些局部(k)malloc'ed变量是一个典型的例子。
我编写的代码只使用了setjmp / longjmp一次。它是在MIDI鼓音序器程序中。回放发生在与所有用户交互不同的进程中,回放过程使用共享内存和UI进程来获取回放所需的有限信息。当用户想要停止播放时,播放过程只是做了一个“回到开头”的longjmp重新开始,而不是在用户希望它停止时执行的任何地方的一些复杂的展开。它工作得很好,很简单,在这种情况下我从来没有遇到任何与之相关的问题或错误。
setjmp / longjmp有他们的位置 - 但是那个地方是你不可能访问的地方,但很长一段时间。
编辑:我只看了代码。它实际上是我使用的siglongjmp(),而不是longjmp(不是说这是一个大问题,但我忘记了siglongjmp甚至存在。)
它永远不会,只要你能够为自己思考。
如果你用C编写VM,事实证明使用(gcc)计算得到的结果是这样的:
bool
比循环内的传统开关工作得快得多。
因为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;
}
可以用于混淆元编程
goto
既是高级也是低级控制表达式,因此它没有适合大多数问题的适当设计模式。
它是低级别的,因为goto是一种原始操作,它可以实现像Goto
或while
之类的东西。
从某种意义上讲,它是高级别的,它以一种不间断的方式,以明确的顺序执行代码,除了结构化循环,并将其转换为具有足够的foreach
s,抓取的逻辑片段 - 动态重组的逻辑包。
因此,goto
有一个平淡和邪恶的一面。
平淡无奇的一面是,向上指向的goto可以实现一个完美合理的循环,向下指向的goto可以做一个完全合理的goto
或break
。当然,实际的return
,while
或break
将更具可读性,因为穷人不必模拟return
的效果以获得全局。所以,一般来说一个坏主意。
邪恶的一方涉及一个例程,不使用goto进行while,break或return,而是将其用于所谓的意大利面条逻辑。在这种情况下,goto-happy开发人员正在从goto的迷宫中构建代码片段,理解它的唯一方法是在整体上模拟它,当有很多goto时,这是一个非常累人的任务。我的意思是,想象一下评估代码的麻烦,其中goto
并不恰恰是else
的反转,其中嵌套的if
s可能允许在外部if
等拒绝的某些东西等。
最后,为了真正涵盖这一主题,我们应该注意到,除了Algol之外,基本上所有早期语言最初只根据其版本的if
进行单一陈述。因此,进行条件块的唯一方法是使用反条件对其周围的if-then-else
。疯了,我知道,但我读过一些陈旧的规格。请记住,第一台计算机是用二进制机器代码编程的,所以我认为任何一种HLL都是救星;我猜他们对于HLL的具体功能并不太挑剔。
说过我曾经把一个goto
粘贴到我编写的每个程序中的所有内容“只是为了惹恼纯粹主义者”。
flex 容器中的绝对定位项仍被视为 IE 和 Firefox 中的项
如果我锁定屏幕或 iPhone 开始休眠,但我从未退出应用程序,那么该应用程序是不是仍被视为后台运行?