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


当前回答

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

具有讽刺意味的是,取消goto引入了一个错误:自旋锁调用被省略了。

其他回答

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

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

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

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

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

具有讽刺意味的是,取消goto引入了一个错误:自旋锁调用被省略了。

如果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,游戏还能完成吗?不。

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

计算现代C程序中goto的数量。现在添加break、continue和return语句的数量。此外,加上你使用if、else、while、switch或case的次数。这是1968年Dijkstra写这封信时,如果你用FORTRAN或BASIC语言编写程序,你的程序会有多少个goto。

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

IF statements had no ELSE. If you wanted one, you had to write: 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 Even if your IF statement didn't need an ELSE, it was still limited to a single line, which usually consisted of a GOTO. There was no DO...LOOP statement. For non-FOR loops, you had to end the loop with an explicit GOTO or IF...GOTO back to the beginning. There was no SELECT CASE. You had to use ON...GOTO.

因此,您的程序中最终出现了许多goto。并且您不能依赖于goto限制在单个子例程中(因为GOSUB…RETURN是一个非常弱的子例程概念),所以这些goto可以去任何地方。显然,这使得控制流难以遵循。

这就是反goto运动的由来。

在我看来,“goto有害”更多的是关于状态的封装和一致性。

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

“goto有害”的问题是,它让程序员只看机制规则而不理解这样的印象,即唯一可用的流控制应该是返回方法,这很容易导致通过引用传递许多状态——这又导致缺乏状态封装,而这正是“goto有害”试图摆脱的东西。

遵循典型的“OO”代码库中的控制流,并告诉我我们仍然没有意大利面条代码....(顺便说一下,我并不是指那些经常让人讨厌的“馄饨”代码——馄饨代码的执行路径通常是非常简单的,即使对象关系不是立即明显的)。

或者,换一种说法,避免gotos而将所有东西都作为子例程,只有在每个子例程只修改局部状态时才有用,只有通过该子例程(或至少该对象)才能修改局部状态。