我知道,除了打破嵌套在循环中的循环之外,其他方法都可以。 goto语句是一种容易出错的编程风格,被人们嘲笑和谴责,永远不要使用。请参阅原始漫画,网址为:http://xkcd.com/292/

,因为我学得很早。对于goto实际导致什么类型的错误,我真的没有任何见识或经验。那么,我们在这里谈论的是什么:


不稳定性?
代码无法维护或无法读取?
安全漏洞?
还有其他东西吗?
“ goto”语句实际上会导致哪种错误?有历史上重要的例子吗?

评论

您是否阅读过Dijkstra关于该主题的原始短文? (这是一个是/否问题,除了明确的即时“是”之外,其他任何答案都是“否”。)

我假设发生灾难性故障时会发出警报。你永远不会回来的地方。像电源故障一样,设备或文件消失。某些类型的“出错时出错” ...但是,我认为如今大多数“几乎是催化的”错误都可以通过“ try-catch”构造来处理。
还没有人提到计算出的GOTO。回到地球年轻的时候,BASIC就有行号。您可以编写以下内容:100 N = A * 100; GOTON。尝试调试:)

@ JohnR.Strohm我已经读过Dijkstra的论文。它写于1968年,建议使用包含诸如switch语句和函数之类的高级功能的语言可以减少对gotos的需求。它是为响应语言而编写的,其中goto是流控制的主要方法,可用于跳转到程序中的任何地方。而撰写本文后开发的语言,例如C,goto只能跳到同一堆栈框架中的位置,并且通常仅在其他选项无法正常工作时使用。 (续...)

(...续)他的观点当时是有效的,但不再相关,无论goto是否是一个好主意。如果我们想争论一种或另一种方式,则必须提出与现代语言有关的论点。顺便问一下,您是否阅读过Knuth的“使用goto语句进行结构化编程”?

#1 楼

这是一种我尚未在其他答案中看到的方法。

它与范围有关。良好编程习惯的主要支柱之一是保持范围狭窄。您需要范围狭窄,因为您缺乏监督和理解的精神能力,而不仅仅是几个逻辑步骤。因此,您创建了小块(每个小块都成为“一件事”),然后使用这些小块进行构建以创建更大的块,这又变成了一件事,依此类推。这样可以使事情易于管理和理解。

goto有效地将逻辑范围扩大到整个程序。这肯定会打败您的大脑,但是除了最小的程序以外,它只跨越几行。并检查你的可怜的小头。这是真正的问题,错误只是可能的结果。

评论


非本地goto?

–重复数据删除器
19年5月2日,19:25

thebrain.mcgill.ca/flash/capsules/experience_jaune03.html <<人类的大脑可以一次平均跟踪7件事。您的基本原理会受到这种限制,因为扩大范围会增加许多需要跟踪的内容。因此,将要引入的错误类型很可能是遗漏和健忘。通常,您还提供了“保持范围紧凑”的缓解策略和良好实践。因此,我接受这个答案。好工作马丁。

– Akiva
19年5月4日在13:47

多么酷啊?!在超过200张选票中,只有1张获胜!

–马丁·马特
19年5月4日在20:03

#2 楼

并不是说goto本身就是坏的。
(毕竟,计算机中的每条跳转指令都是goto。)
问题在于,人的编程风格早于结构化编程,也可以称为“流程图”编程。用于执行语句,用于决策的菱形,您可以将它们与遍布各处的线连接起来。 (所谓的“意大利面条代码”。)

意大利面条代码的问题在于,您(程序员)可以“知道”它是正确的,但是您如何向自己或其他人证明这一点? ?
实际上,它实际上可能有潜在的不当行为,而您对它始终正确的认识可能是错误的。

伴随着带有起点到终点的块的结构化编程的兴起, if-else,依此类推。
这些优点是您仍然可以在其中执行任何操作,但是如果您非常小心,则可以确保您的代码正确。

当然,即使没有goto,人们仍然可以编写意大利面条式代码。常见的方法是编写while(...) switch( iState ){...,其中不同的情况会将iState设置为不同的值。实际上,在C或C ++中,可以编写一个宏来执行此操作,并将其命名为GOTO,因此,说您不使用goto是一种区别,没有区别。

验证可以排除无限制的goto,很久以前,我偶然发现了一种可用于动态更改用户界面的控制结构。
我称其为差分执行。取决于纯结构化编程-否gotoreturncontinuebreak或异常。



评论


除了在最琐碎的程序中,即使使用结构化程序编写它们,也无法证明其正确性。您只能提高信心水平;结构化编程确实可以做到这一点。

–罗伯特·哈维(Robert Harvey)
16-10-23在22:24



@MikeDunlavey:相关:“当心上面代码中的错误;我只证明了它是正确的,没有尝试过。” staff.fnwi.uva.nl/p.vanemdeboas/knuthnote.pdf

–鸭鸭
16-10-24在0:39

@RobertHarvey正确性证明仅适用于琐碎的程序并不完全正确。但是,需要比结构化编程更专业的工具。

–user251204
16-10-24在5:09

@MSalters:这是一个研究项目。这样的研究项目的目的是表明,如果他们有资源,他们可以编写经过验证的编译器。如果查看他们当前的工作,您会发现他们对支持更高版本的C根本不感兴趣,而是对扩展可以证明的正确性属性感兴趣。因此,它们是否支持C90,C99,C11,Pascal,Fortran,Algol或Plankalkül完全无关紧要。

–Jörg W Mittag
16-10-24在8:06

@martineau:我对那些“玻色子短语”持怀疑态度,程序员在某种程度上同意它的好坏,因为如果不这样做,他们会坐下来。事情必须有更基本的原因。

–迈克·邓拉维(Mike Dunlavey)
16-10-24在16:31

#3 楼

为什么goto危险?




goto本身不会引起不稳定。尽管有大约100,000个goto,但Linux内核仍然是稳定的模型。

goto本身不应该引起安全漏洞。但是,在某些语言中,将其与try / catch异常管理块混合使用可能会导致漏洞,如本CERT建议中所述。主流C ++编译器会标记并防止此类错误,但不幸的是,较早或更旧的编译器不会这样做。

goto导致无法读取和无法维护的代码。这也称为意大利面条代码,因为就像意大利面条盘一样,当有太多的getos时很难遵循控制流程。

即使您设法避免使用意大利面条代码,并且仅使用少数gotos,它们仍然会促进类似bug和资源泄漏的作用。它的控制流程非常可预测。因此,更容易确保不变量得到尊重。
通过goto语句,您可以打破那种直截了当的流程,并打破期望。例如,您可能没有注意到仍然需要释放资源。
许多在不同地方的goto可以将您发送到单个goto目标。因此,确定到达此位置时所处的状态并不明显。因此,做出错误/毫无根据的假设的风险非常大。

其他信息和引语:


E.Dijkstra撰写了一篇有关该主题的早期文章。 1968年:“发表声明被认为有害”
Brian.W.Kernighan和Dennis.M.Ritchie用C编程语言写道:语句和标签到
分支到。正式不需要goto,实际上它是
没有它几乎总是很容易编写代码。 (...)
尽管如此,我们还是会建议goto可能会找到位置的一些情况。最常见的用途是放弃某些深度嵌套结构中的处理,例如一次中断两个循环。 (...)
尽管我们对这件事不是很教条,但似乎应该尽量少用goto语句。




James Gosling和Henry McGilton在其1995 Java语言环境白皮书中写道:


No More Goto语句Java没有goto语句。研究表明,goto被(误用)的原因不只是“因为它存在”,而更多。消除goto导致语言的简化
(...)对大约100,000行C代码的研究确定,
大约90%的goto语句纯粹用于获得效果摆脱嵌套循环的问题。如上所述,
多级中断并继续删除了goto
语句的大部分需求。这些引人入胜的术语是:


goto-臭名昭著的goto。主要在机器生成的C ++代码中有用。



什么时候可以使用goto?我承认在某些情况下goto可以减轻生活负担。

典型地,在C语言中,goto允许多级循环退出,或者错误处理要求到达适当的退出点才能释放/解锁所有资源到目前为止已分配的数量(即依次分配意味着多个标签)。本文量化了Linux内核中goto的不同用法。

我个人比较喜欢避免这种情况,并且在使用C语言的10年中,我最多使用10个goto。我更喜欢使用嵌套的goto,我认为它更具可读性。当这会导致嵌套太深时,我会选择将函数分解为较小的部分,或者在级联中使用布尔值指示器。当今的优化编译器足够聪明,可以生成几乎与if相同的代码。

goto的使用很大程度上取决于该语言: C ++,正确使用RAII会导致编译器自动销毁超出范围的对象,因此无论如何都将清除资源/锁,并且不再需要goto。
在Java中,无需goto (请参阅上面的Java作者报价以及这个出色的Stack Overflow答案):清理混乱的垃圾收集器,gotobreakcontinue / try异常处理涵盖了catch可能会有所帮助的所有情况,但是以一种更安全,更好的方式。 Java的流行证明可以用现代语言避免goto语句。

放大著名的SSL goto fail漏洞

重要免责声明:鉴于评论中的激烈讨论,我想澄清一下,我并不假装goto语句是导致此错误的唯一原因。我不假装没有goto就不会有bug。我只想表明goto可能与严重的错误有关。但是,有一个著名的Apple SSL错误削弱了iOS的安全性。导致此错误的语句是错误的goto语句。

一些人认为,错误的根本原因不是goto语句本身,而是错误的复制/粘贴,误导性的缩进,条件块周围缺少花括号,或者开发人员的工作习惯。我也无法确认其中任何一个:所有这些论点都是可能的假设和解释。没人真正知道。 (与此同时,鉴于同一功能中的其他缩进不一致,似乎有人在评论中建议合并错误的假设似乎是一个很好的选择。)

唯一客观的事实是重复的goto导致过早退出功能。查看代码,唯一可能引起相同效果的其他语句将是返回。

此文件中的函数goto中存在错误:

< preclass =“ lang-c prettyprint-override”> goto

条件块周围的花括号确实可以防止该错误:它可能导致编译时出现语法错误(并进行更正)或多余的无害转到。顺便说一句,由于其可选的警告来检测不一致的缩进,GCC 6将能够发现这些错误。但是首先,可以通过结构化代码避免所有这些问题。因此,goto至少间接地是导致此错误的原因。至少有两种不同的方法可以避免这种情况:

方法1:if子句或嵌套的SSLEncodeSignedServerKeyExchange() s

而不是依次测试许多错误条件,每种如果在出现问题的情况下发送给 if ((err = ReadyHash(&SSLHashSHA1, &hashCtx)) != 0) goto fail; if ((err =...) !=0) goto fail; if ((err = SSLHashSHA1.update(&hashCtx, &signedParams)) != 0) goto fail; goto fail; // <====OUCH: INDENTATION MISLEADS: THIS IS UNCONDITIONDAL!! if (...) goto fail; ... // Do some cryptographic operations here fail: ... // Free resources to process error 标签,则可以选择在if-语句中执行加密操作,该语句仅在没有错误的前提条件时才执行: =“ lang-c prettyprint-override”> fail

方法2:使用错误累加器

这种方法基于这样一个事实,即几乎所有的语句都调用某个函数来设置if错误代码,并且仅在 if ((err = ReadyHash(&SSLHashSHA1, &hashCtx)) == 0 && (err = ...) == 0 ) && (err = ReadyHash(&SSLHashSHA1, &hashCtx)) == 0) && ... (err = ...) == 0 ) ) { ... // Do some cryptographic operations here } ... // Free resources 为0时才执行其余代码(即,执行的函数没有错误)。一个很好的安全且易读的替代方法是: :没有风险迅速跳到故障退出点。而且在视觉上,很容易发现未对齐的线或被遗忘的err

此结构更紧凑。基于这样的事实,在C中,仅当第一部分为true时,才对逻辑和(err)的第二部分求值。实际上,由优化编译器生成的汇编器几乎等效于使用gotos的原始代码:优化器能够很好地检测条件链并生成代码,该代码在第一个非null返回值跳到末尾(在线证明)。

您甚至可以设想在功能结束时进行一致性检查,以便在测试阶段识别ok标志和错误代码之间的不匹配。

>
在调试阶段很容易发现错误的bool ok = true; ok = ok && (err = ReadyHash(&SSLHashSHA1, &hashCtx))) == 0; ok = ok && (err = NextFunction(...)) == 0; ... ok = ok && (err = ...) == 0; ... // Free resources 替换为ok &&或逻辑连接器错误。任何错误。我只想说,他们本来可以使此错误更难以发生。

评论


不,Apple goto失败错误是由聪明的程序员决定在if语句后不包括花括号引起的。 :-)因此,当下一个维护程序员(或相同的程序员,相同的区别)出现并添加了仅当if语句评估为true时才应该执行的另一行代码,该语句每次都运行。 Bam,“执行失败”错误,这是一个主要的安全错误。但这实际上与使用goto语句无关。

– Craig
16-10-23在23:18



Apple的“ goto失败”错误直接由重复的一行代码引起。该错误的特殊影响是由于该行是无括号if语句中的goto。但是,如果您建议避免使用goto将使您的代码不受随机重复行的影响,那么我对您来说是个坏消息。

–user253751
16-10-23在23:43



您正在使用快捷布尔操作符行为来通过副作用执行一条语句,并声称它比if语句更清晰吗?娱乐时间。 :)

– Craig
16-10-24在7:24



如果不是“ goto失败”而是“ failed = true”的声明,那将发生完全相同的事情。

– gnasher729
16-10-24在8:06

该错误是由草率的开发人员不注意自己所做的事情,并且没有阅读自己的代码更改引起的。仅此而已。好吧,也许在那里也进行一些测试监督。

–轨道轻赛
16-10-24在11:33



#4 楼

著名的Dijkstra文章是在某些编程语言实际上能够创建具有多个入口和出口点的子例程时编写的。换句话说,您实际上可以跳入一个函数的中间,然后跳出该函数中的任何位置,而无需实际调用该函数或以常规方式从该函数返回。汇编语言仍然如此。从来没有人认为这样的方法优于我们现在使用的结构化编写软件的方法。入口点是您为函数指定参数并调用它的地方,出口点是您返回结果值并继续执行原始函数调用后的指令的地方。

在该功能内,您应该能够在合理范围内做任何您想做的事情。如果在函数中添加一两个goto可以使其更清晰或提高速度,那为什么不呢?函数的重点是隔离一些明确定义的功能,这样您就不必再考虑它在内部如何工作了。一旦编写,就可以使用它。

是的,在一个函数中可以有多个return语句。返回的适当函数中始终仍然有一个位置(基本上是该函数的背面)。这与在goto有机会正确返回之前跳出goto函数完全不一样。这是关于避免滥用它们。每个人都同意,您可以使用gotos制作出令人费解的程序,但是您也可以通过滥用功能来做到这一点(滥用gotos容易得多)。值得一提的是,自从我从行号样式的BASIC程序毕业到使用Pascal和花括号语言的结构化编程以来,我再也不需要使用goto。我唯一一次尝试使用它的方法是从嵌套循环中进行早期退出(在不支持从循环中进行多级早期退出的语言中),但是我通常可以找到另一种更干净的方式。 br />

评论


评论不作进一步讨论;此对话已移至聊天。

– Maple_shaft♦
16-10-27在18:40

#5 楼


“ goto”语句会导致哪种错误?有没有历史上的重要例子?

我从小就以编写BASIC程序的方式使用goto语句作为获取和while循环的简单方法(Commodore 64 BASIC没有while循环,而且我还不太成熟,无法学习for循环的正确语法和用法。 Python,一种已经确定不需要goto的高级编程语言。
1968年Edsger Dijkstra宣布“ Goto认为是有害的”时,他没有举几个例子来归咎于相关的bug。相反,他宣称,对于高级语言而言,不需要goto,因此应避免使用goto,而应使用我们今天认为的正常控制流程:循环和条件。他的话是:

go to的不加限制的使用立即产生了后果,即很难找到有意义的坐标来描述过程进度。
[... ]
目前的go语句太原始了;

他每次使用goto调试代码时,可能都有大量的错误示例。但是他的论文是一个概括的立场声明,以证明goto对于高级语言而言是不必要的。普遍存在的错误是您可能无法静态分析所查询的代码。
使用goto语句进行静态分析的代码要困难得多,尤其是当您跳回控制流(我曾经这样做)或到一些不相关的代码部分。代码可以并且是通过这种方式编写的。这样做是为了对非常稀缺的计算资源以及机器的体系结构进行高度优化。
伪经示例
有一个二十一点程序,维护人员发现该程序经过了非常优雅的优化,但是由于代码的性质,他也无法对其进行“修复”。它是用很大程度上依赖于goto的机器代码编程的-所以我认为这个故事很有意义。这是我能想到的最好的规范示例。
反例
但是,CPython的C源代码(最常见和参考的Python实现)使用q4312079q语句效果很好。它们用于绕过函数内部无关的控制流以到达函数末尾,从而使函数更有效而又不会损失可读性。这符合结构化编程的理想之一-为函数提供单个出口。
记录下来,我发现这个反例非常容易静态分析。

评论


我遇到的“梅尔故事”是一个奇怪的体系结构,其中(1)每条机器语言指令在其操作后都会执行跳转,并且(2)将指令序列放置在连续的内存位置中效率极低。该程序是用手动优化的程序集编写的,而不是经过编译的语言。

–user122173
16-10-24在4:23



@MilesRout我认为您在这里可能是正确的,但是有关结构化编程的Wikipedia页面上说:“在许多语言中,最常见的偏差是使用return语句使子例程提前退出。这会导致多个退出点,而不是结构化编程所需的单个退出点。” -您是在说错吗?您有资料来源吗?

–亚伦·霍尔
16-10-26在14:23



@AaronHall那不是本来的意思。

–Miles Rout
16-10-27在9:45

#6 楼

当棚屋是当前的建筑艺术时,他们的建造者无疑可以为您提供关于棚屋的构造,如何使烟雾散逸等实用的建议。幸运的是,今天的建筑商可能会忘记大部分建议。

当驿马车是当前的运输艺术品时,他们的司机无疑可以为您提供关于驿马车的马匹,如何防御高速公路工等的实用建议。上。幸运的是,今天的驾驶者也可以忘记大部分建议。

当打孔卡是当前编程艺术时,他们的从业人员同样可以为您提供关于卡的组织,如何对语句进行编号等实用的建议。上。我不确定今天的建议是否非常有用。您不需要知道它,但是如果您不知道,那么您对goto的警告主要相关的历史背景是不熟悉的。今天不是很相关。通过while / for循环和函数调用的基本培训,您甚至都不会考虑经常发布goto。当您想到它时,可能有一个原因,所以继续。

但是可以不滥用goto语句吗? ,但与远远不那么常见的错误(例如在常量将用作变量的变量)或剪切和粘贴编程(否则称为忽略重构)相比,它的滥用在软件工程中是很小的问题。我怀疑你有很大的危险。除非您使用goto或以其他方式将控制权转移到遥远的代码,否则如果您认为要使用longjmp或只是想尝试娱乐,请继续。没事的。

您可能会注意到缺乏最近的恐怖故事,其中goto扮演小人。这些故事中的大多数或全部似乎都是30或40年历史的。如果您认为这些故事大多已过时,则可以站在坚实的基础上。

评论


我发现我至少吸引了一位选民。好吧,这是意料之中的。可能会有更多的投票表决。但是,当数十年的编程经验告诉我,在这种情况下,常规答案碰巧是错误的,因此我不会给出常规答案。无论如何,这是我的看法。我已经说明了原因。读者可以决定。

–thb
16-10-25在16:55



您的帖子太好了,太糟糕了,我不能拒绝评论。

– Pieter B
16年11月15日在9:58

#7 楼

要在其他出色的答案中添加一件事,请使用goto语句,很难确切说明您如何到达程序中的任何指定位置。您可以知道某个特定行发生了异常,但是如果代码中存在goto,则无法在不搜索整个程序的情况下就知道执行了哪些语句导致此异常导致状态。没有调用堆栈,也没有视觉流程。可能有一条语句在1000行之外,这使您处于错误状态,对引发异常的行执行goto

评论


Visual Basic 6和VBA具有痛苦的错误处理,但是如果使用On Error Goto失败,则可以使用Resume重试,使用Resume Next跳过有问题的行,并使用Erl知道它在哪一行(如果使用行号)。

– Cees Timmerman
16-10-24在8:47

@CeesTimmerman我对Visual Basic所做的很少,我不知道。我将对此添加评论。

– qfwfq
16-10-24在15:24

@CeesTimmerman:如果子例程中没有自己的出错错误块,则发生错误时,“恢复”的VB6语义将令人恐惧。 Resume将重新开始执行该功能,Resume Next将跳过该功能中尚未执行的所有操作。

–超级猫
16-10-24在21:49

@supercat就像我说的那样,很痛苦。如果您需要知道确切的行,则应将它们全部编号,或使用On Error Goto 0(临时错误转到0)和手动调试暂时禁用错误处理。恢复应在检查Err.Number并进行必要的调整后使用。

– Cees Timmerman
16-10-25在8:16

我应该注意,在现代语言中,goto仅适用于带标记的行,而这似乎已被带标记的循环所取代。

– Cees Timmerman
16-10-25在8:25

#8 楼

与其他形式的流控制相比,goto难以让人理解。

编写正确的代码很困难。编写正确的程序很困难,确定程序是否正确很难,证明程序正确是困难的。 br /> goto通过获取代码来完成某些程序,以实现所需的功能。它没有帮助使正确性检查变得容易,而它的替代方法却经常这样做。甚至在编程风格中,邪恶的孪生子都是合适的解决方案。在这两种情况下,都必须格外小心,以确保您以可理解的模式使用它,并且要进行大量的手动检查,以确保您使用它的正确性并没有困难。

例如,某些语言有一个称为协程的功能。协程是无线程线程;执行状态,没有线程可以对其运行。您可以要求他们执行,然后他们可以运行自己的一部分,然后中止自己的工作,交还流控制。和C)可以混合使用goto和手动状态管理。可以使用C的setjmplongjmp函数来完成“深层”协程。对于C ++,人们发现它们足够有用,以至于扩展了语言来支持它们。 goto和手动状态管理被隐藏在零成本抽象层的后面,使程序员可以编写它们而不必证明自己的goto,void**混乱,手动构造/破坏状态等是正确的。 />
goto隐藏在更高级别的抽象之后,例如whileforifswitch。那些较高级别的抽象更容易证明正确和检查。

如果语言缺少其中的一些(例如某些现代语言缺少协程),则要么随形随形,要么不适合问题或使用goto成为您的替代选择。编写可靠,健壮的代码非常困难。 Goto对第一个的帮助远大于对第二个的帮助。因此,“ goto认为是有害的”,因为这是表面上“工作”的代码的征兆,其中包含深层且难以跟踪的错误。通过足够的努力,您仍然可以使带有“ goto”的代码可靠地健壮,因此,将as规则保持为绝对是错误的。但根据经验,这是一个很好的选择。

评论


longjmp仅在C语言中合法,可将堆栈退回到父函数中的setjmp。 (就像打破了多层嵌套一样,但是对于函数调用而不是循环)。从此函数返回的setjmp中将longjjmp转换为上下文是不合法的(我怀疑在大多数情况下在实践中不会起作用)。我不熟悉协例程,但是从您对类似于用户空间线程的协例程的描述中,我认为它将需要自己的堆栈以及已保存的寄存器上下文,并且longjmp仅给您保存寄存器,而不是单独的堆栈。

– Peter Cordes
16-10-24在16:29

哦,我应该刚刚用Google搜索过:fanf.livejournal.com/105413.html描述了为协程运行运行的堆栈空间。 (我怀疑这是仅使用setjmp / longjmp的必要条件)。

– Peter Cordes
16-10-24在16:33



@PeterCordes:不需要实现提供任何方法来使代码创建适合用作协程的对jmp_buff。某些实现确实指定了一种方法,即使标准不要求代码提供这种功能,代码也可以通过这种方式来创建代码。我不会描述依赖于诸如“标准C”之类的技术的代码,因为在许多情况下jmp_buff将是不透明的结构,不能保证在不同的编译器之间表现一致。

–超级猫
16-10-25在0:43



#9 楼

从http://www-personal.umich.edu/~axe/research/Software/CC/CC2/TourExec1.1.f.html看一下这段代码,它实际上是大型囚徒困境模拟的一部分。如果您看过旧的FORTRAN或BASIC代码,您会意识到它并不稀奇。

 C  Not nice rules in second round of tour (cut and pasted 7/15/93)
   FUNCTION K75R(J,M,K,L,R,JA)
C  BY P D HARRINGTON
C  TYPED BY JM 3/20/79
   DIMENSION HIST(4,2),ROW(4),COL(2),ID(2)
   K75R=JA       ! Added 7/32/93 to report own old value
   IF (M .EQ. 2) GOTO 25
   IF (M .GT. 1) GOTO 10
   DO 5 IA = 1,4
     DO 5 IB = 1,2
5  HIST(IA,IB) = 0

   IBURN = 0
   ID(1) = 0
   ID(2) = 0
   IDEF = 0
   ITWIN = 0
   ISTRNG = 0
   ICOOP = 0
   ITRY = 0
   IRDCHK = 0
   IRAND = 0
   IPARTY = 1
   IND = 0
   MY = 0
   INDEF = 5
   IOPP = 0
   PROB = .2
   K75R = 0
   RETURN

10 IF (IRAND .EQ. 1) GOTO 70
   IOPP = IOPP + J
   HIST(IND,J+1) = HIST(IND,J+1) + 1
   IF (M .EQ. 15 .OR. MOD(M,15) .NE. 0 .OR. IRAND .EQ. 2) GOTO 25
   IF (HIST(1,1) / (M - 2) .GE. .8) GOTO 25
   IF (IOPP * 4 .LT. M - 2 .OR. IOPP * 4 .GT. 3 * M - 6) GOTO 25
   DO 12 IA = 1,4
12 ROW(IA) = HIST(IA,1) + HIST(IA,2)

   DO 14 IB = 1,2
     SUM = .0
     DO 13 IA = 1,4
13   SUM = SUM + HIST(IA,IB)
14 COL(IB) = SUM

   SUM = .0
   DO 16 IA = 1,4
     DO 16 IB = 1,2
       EX = ROW(IA) * COL(IB) / (M - 2)
       IF (EX .LE. 1.) GOTO 16
       SUM = SUM + ((HIST(IA,IB) - EX) ** 2) / EX
16 CONTINUE

   IF (SUM .GT. 3) GOTO 25
   IRAND = 1
   K75R = 1
   RETURN

25 IF (ITRY .EQ. 1 .AND. J .EQ. 1) IBURN = 1
   IF (M .LE. 37 .AND. J .EQ. 0) ITWIN = ITWIN + 1
   IF (M .EQ. 38 .AND. J .EQ. 1) ITWIN = ITWIN + 1
   IF (M .GE. 39 .AND. ITWIN .EQ. 37 .AND. J .EQ. 1) ITWIN = 0
   IF (ITWIN .EQ. 37) GOTO 80
   IDEF = IDEF * J + J
   IF (IDEF .GE. 20) GOTO 90
   IPARTY = 3 - IPARTY
   ID(IPARTY) = ID(IPARTY) * J + J
   IF (ID(IPARTY) .GE. INDEF) GOTO 78
   IF (ICOOP .GE. 1) GOTO 80
   IF (M .LT. 37 .OR. IBURN .EQ. 1) GOTO 34
   IF (M .EQ. 37) GOTO 32
   IF (R .GT. PROB) GOTO 34
32 ITRY = 2
   ICOOP = 2
   PROB = PROB + .05
   GOTO 92

34 IF (J .EQ. 0) GOTO 80
   GOTO 90

70 IRDCHK = IRDCHK + J * 4 - 3
   IF (IRDCHK .GE. 11) GOTO 75
   K75R = 1
   RETURN

75 IRAND = 2
   ICOOP = 2
   K75R = 0
   RETURN

78 ID(IPARTY) = 0
   ISTRNG = ISTRNG + 1
   IF (ISTRNG .EQ. 8) INDEF = 3
80 K75R = 0
   ITRY = ITRY - 1
   ICOOP = ICOOP - 1
   GOTO 95

90 ID(IPARTY) = ID(IPARTY) + 1
92 K75R = 1
95 IND = 2 * MY + J + 1
   MY = K75R
   RETURN
   END
 


这里有很多问题,远远超出了GOTO声明;老实说,我认为GOTO声明有点替罪羊。但是,这里的控制流程绝对不清楚,并且代码混合在一起的方式非常不清楚正在发生的事情。即使不添加注释或使用更好的变量名,将其更改为没有GOTO的块结构也将使其更易于阅读和遵循。

评论


如此真实:-)可能值得一提:传统的FORTAN和BASIC没有块结构,因此,如果THEN子句不能放在一行上,则GOTO是唯一的选择。

–克里斯托弗(Christophe)
16-10-24在18:12

用文本标签代替数字,或者至少在数字行上添加注释,将大有帮助。

– Cees Timmerman
16-10-25在9:48

回到我的FORTRAN-IV时代:我限制使用GOTO来实现IF / ELSE IF / ELSE块,WHILE循环以及BREAK和NEXT循环。有人向我展示了意大利面条代码的弊端,以及为什么即使必须使用GOTO实现块结构,也应该构造以避免这种结构的代码。后来我开始使用预处理器(RATFOR,IIRC)。 Goto本质上不是邪恶的,它只是映射到汇编器分支指令的功能强大的低级构造。如果由于语言不足而必须使用它,则可以使用它来构建或扩充适当的块结构。

–nigel222
16-10-25在10:06

@CeesTimmerman:那是各种花园的FORTRAN IV代码。 FORTRAN IV不支持文本标签。我不知道该语言的最新版本是否支持它们。标准FORTRAN IV不支持与代码在同一行上的注释,尽管可能存在特定于供应商的扩展来支持它们。我不知道“当前”的FORTRAN标准怎么说:很久以前我放弃了FORTRAN,但我不会错过它。 (我见过的最新FORTRAN代码与1970年代初期的PASCAL非常相似。)

– John R. Strohm
16-10-25在18:44

@CeesTimmerman:特定于供应商的扩展。一般而言,该代码对注释真的很害羞。另外,有一种约定在某些地方变得很普遍,即仅在CONTINUE和FORMAT语句上放置行号,以使以后添加行更加容易。此代码不遵循该约定。

– John R. Strohm
16-10-25在19:39

#10 楼

可维护编程的原理之一就是封装。关键是您可以使用已定义的接口(仅该接口)与模块/例程/子例程/组件/对象进行接口,并且结果是可预测的(假定已对单元进行了有效测试)。

在单个代码单元内,适用相同的原理。如果您一贯地应用结构化编程或面向对象的编程原理,则不会:


通过代码创建意外路径
到达带有未定义或不允许的变量值的代码部分
使用未定义或不允许的变量值退出代码的路径
无法完成事务单元
将代码或数据中实际上已死掉但不符合垃圾清理和重新分配的部分,因为您还没有使用显式构造来发出释放信号,而是在代码控制路径退出单元时调用释放它们

这些处理错误的一些较常见的症状包括内存泄漏,内存ho积,指针溢出,崩溃,不完整的数据记录,记录添加/更改/删除异常,内存页面错误等。

这些问题的用户可观察到的表现包括用户界面锁定,逐渐降低的性能,不完整的数据记录,无法启动或完成事务,数据损坏,网络中断,断电,嵌入式系统故障(从导弹失控到空中交通管制系统中跟踪和控制能力的丧失),计时器故障等等。目录非常广泛。

评论


您回答一次都没有提及goto。 :)

–罗伯特·哈维(Robert Harvey)
16-10-24在23:03

#11 楼

goto很危险,因为它通常在不需要的地方使用。使用不需要的任何东西都是危险的,但是特别要注意。如果您使用google,您会发现很多由goto引起的错误,这并不是不使用它的原因(错误通常在您使用语言功能时发生,因为这是编程中固有的),但是其中一些显然是高度相关的


使用/不使用goto的原因:


如果需要循环,则必须使用whilefor
如果需要条件跳转,请使用if/then/else
如果需要过程,请调用函数/方法。
如果需要退出函数,只需return

我可以指指看过goto并已正确使用和使用的地方。


CPython
libKTX
可能要少一些

libKTX中有一个具有以下代码的函数



 if(something)
    goto cleanup;

if(bla)
    goto cleanup;

cleanup:
    delete [] array;
 


现在在这里goto很有用,因为语言是C:


我们在函数内
我们不能编写一个清理函数(因为我们进入另一个作用域,并且使可访问的调用者函数状态更加繁重)

此用例很有用,因为在C中我们没有类,因此最简单的清理方法是使用goto

如果我们在C ++中使用相同的代码,则不再需要goto: / pre>


它会导致哪种错误?没事无限循环,错误的执行顺序,堆栈螺钉。.

一个已记录的案例是SSL中的一个漏洞,该漏洞使Man可以通过错误使用goto进行中间攻击:这是文章

这是一个错字错误,但有一段时间没有引起注意,如果代码以其他方式构造,则无法正确测试这种错误。

评论


一个答案有很多注释,这些注释非常清楚地说明了“ goto”的使用完全与SSL错误无关-该错误是由不小心重复一行代码引起的,因此,它曾经有条件地执行,而无条件地执行了一次。

– gnasher729
16-10-26在23:08

是的,在我接受之前,我正在等待一个更好的goto错误历史示例。如前所述那行代码到底什么都没关系;缺少花括号将导致它执行并导致错误。不过我很好奇。什么是“堆叠螺丝”?

– Akiva
16-10-26在23:28

我以前在PL / 1 CICS中工作的代码示例。程序弹出一个由单线图组成的屏幕。当屏幕返回时,它找到了已发送的地图数组。使用该元素在一个数组中查找索引,然后将该索引用于数组变量的索引以执行GOTO操作,以便可以处理该单个映射。如果发送的地图数组不正确(或已损坏),则它可能会尝试对错误的标签执行GOTO操作,否则可能会崩溃,因为它在标签变量数组的末尾访问了元素。重写为用例类型语法。

–启动
16-10-27在11:16