是否有可能总是消除goto?



虽然用goto实现所有都很容易(如f.ex.IL所证明的),但我想知道是否也可以使用Java中支持的一切,用更高级别的表达式和语句消除所有的goto语句。

或者,如果你喜欢的话:我想要的是"重写规则",无论goto是如何创建的,它都将始终有效。

它主要是作为一个理论问题,纯粹是作为兴趣;不是一种好的/坏的做法。

我想到的显而易见的解决方案是使用这样的东西:

while (true)
{
    switch (state) {
       case [label]: // here's where all your goto's will be
          state = [label];
          continue;
       default:
          // here's the rest of the program.
    }
}

虽然这可能会奏效,而且确实符合我的"正式"问题,但我一点也不喜欢我的解决方案。首先,它非常丑陋;其次,它基本上将goto封装成一个与goto完全相同的开关

那么,有更好的解决方案吗?


更新1

由于很多人似乎认为这个问题"过于宽泛",我将进一步阐述。。。我之所以提到Java,是因为Java没有"goto"语句。作为我的一个业余项目,我试图将C#代码转换为Java,这被证明是非常具有挑战性的(部分原因是Java中的这种限制)。

这让我思考。如果您在Open addressing中实现了"remove"方法(参见:http://en.wikipedia.org/wiki/Open_addressing-注意1),在特殊情况下使用"goto"非常方便,尽管在这种特殊情况下,您可以通过引入"state"变量来重写它。注意,这只是一个例子,我实现了用于continuations的代码生成器,当你试图反编译它们时,它会产生大量的goto。

我也不确定在这个问题上的重写是否总是会消除"goto"语句,以及它是否在所有情况下都被允许。虽然我不是在寻找一个正式的"证据",但一些证据表明在这件事上消除是可能的,那就太好了。

因此,关于"广度",我向所有认为"答案太多"或"改写goto的方法太多"的人提出挑战,请提供一种算法或改写一般情况的方法,因为到目前为止,我找到的唯一答案是我发布的答案。

这篇1994年的论文:驯服控制流:消除后藤的结构化方法Statements提出了一种算法来消除C程序中的所有goto语句。该方法适用于任何用C#编写的程序,也适用于任何使用if/switch/roop/break/continue(AFAIK,但我不明白为什么它不会)等常见结构的语言。

它从两个最简单的转换开始:

  • 案例1

    Stuff1();
    if (cond) goto Label;
    Stuff2();
    Label:
    Stuff3();
    

    变为:

    Stuff1();
    if (!cond)
    {
      Stuff2();
    }
    Stuff3();
    
  • 案例2

    Stuff1();
    Label:
    Stuff2();
    Stuff3();
    if (cond) goto Label;
    

    变为:

    Stuff1();
    do
    {
      Stuff2();
      Stuff3();
    } while (cond);
    

并在此基础上检查每个复杂的情况,并应用迭代转换来产生这些琐碎的情况。然后,它以最终的gotos/标签根除算法结束。

这是一本非常有趣的读物。

更新:关于这个主题的其他一些有趣的论文(不容易获得,所以我在这里复制了直接链接供参考):

删除Goto语句的正式基础

McCat C编译器的Goto消除方法及其实现

一种可以避免Goto的情况,但我认为最好使用它:

当我需要退出内部循环和循环时:

for(int i = 0; i < list.Count; i++)
{
    // some code that initializes inner
    for(int j = 0; j < inner.Count; j++)
    {
       // Some code.
       if (condition) goto Finished;
    }
}
Finished:
// Some more code.

为了避免goto,你应该这样做:

for(int i = 0; i < list.Count; i++)
{
    // some code that initializes inner
    bool conditon = false;
    for(int j = 0; j < inner.Count; j++)
    {
       // Some code that might change condition
       if (condition) break;
    }
    if (condition) break;
}
// Some more code.

我认为有了后藤的声明看起来好多了。

如果内部循环使用不同的方法,则第二种情况是可以的。

void OuterLoop(list)
{
    for(int i = 0; i < list.Count; i++)
    {
        // some code that initializes inner
        if (InnerLoop(inner)) break;
    }
}
bool InnerLoop(inner)
{
    for(int j = 0; j < inner.Count; j++)
    {
       // Some code that might change condition
       if (condition) return true;
    }
    return false;
}

我有一些实践经验,尝试采用非结构化程序(COBOL),并通过删除GOTO的每个实例将其呈现为结构化程序。最初的程序员是一个经过改造的Assembly程序员,虽然他可能知道PERFORM语句,但他没有使用它。这是一个严肃的意大利面条代码——几百行的意大利面条编码。我花了几个星期的业余时间试图改写这个可怕的结构,最终我不得不放弃。这是一大堆疯狂的东西。不过效果不错!它的工作是解析以文本格式发送到大型机的用户指令,而且做得很好。

所以,不,完全消除GOTO并不总是可能的——如果你使用手动方法。然而,这是一个边缘案例——现有的代码是由一个显然有着扭曲编程头脑的人编写的。在现代,有一些工具可以解决以前难以解决的结构问题。

从那天起,我就用Modula-2、C、启示基础、VB的三种风格和C#进行了编码,从未发现需要甚至建议将GOTO作为解决方案的情况。然而,对于最初的BASIC来说,GOTO是不可避免的。

既然你说这是一个理论问题,下面是理论答案。

Java是图灵完备的,所以,是的。您可以用Java表达任何C#程序。你也可以在Minecraft红石或扫雷器中表达它。与这些替代方案相比,用Java表达它应该很容易。

然而,Patrice给出了一个明显更实用的答案,即一个可理解的转换算法。

我一点也不喜欢我的解决方案。首先,它非常丑陋;其次,它基本上将goto封装成一个与goto完全相同的开关

当你寻找一个可以取代goto的任何使用的通用模式时,你总是会得到这一点,它的作用与goto完全相同。它还能是什么?goto的不同用法应该用最匹配的语言结构来代替。这就是为什么首先存在作为切换语句和for循环的构造,以使创建这样的程序流更容易且不易出错。编译器仍然会生成goto(或跳转),但在我们会出错的地方,它会一直这样做。除此之外,我们不必阅读编译器生成的内容,但我们可以阅读(和编写)更容易理解的内容。

你会发现,大多数编译器构造的存在都是为了概括goto的特定用途,这些构造是基于之前存在的goto使用的常见模式创建的。Patrice Gahide提到的论文在某种程度上反过来说明了这一过程。如果您在代码中找到goto,那么您要么查看其中一个模式,在这种情况下,您应该用匹配的语言构造来替换它。或者您看到的是本质上非结构化的代码,在这种情况下,您应该实际构建代码(或者不考虑它)。将其更改为非结构化但没有goto的内容只会使情况变得更糟。(顺便说一句,同样的泛化过程仍在进行,请考虑如何将foreach添加到编译器中,以泛化for的一个非常常见的用法…)

相关内容

  • 没有找到相关文章

最新更新