6

虽然使用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 中的这种限制)。

这让我思考。如果你有 f.ex. 开放寻址中'remove'方法的实现(参见:http ://en.wikipedia.org/wiki/Open_addressing - 注1),在特殊情况下使用'goto'非常方便,尽管在这个特殊的情况下如果您可以通过引入“状态”变量来重写它。请注意,这只是一个示例,我已经为延续实现了代码生成器,当您尝试对它们进行反编译时,它们会产生大量的 goto。

我也不确定在这件事上重写是否总是会消除“goto”语句,以及是否在每种情况下都允许。虽然我不是在寻找正式的“证据”,但有一些证据表明在这件事上可以消除是很好的。

所以关于“广泛性”,我挑战所有认为有“太多答案”或“重写 goto 的多种方法”的人,以提供一种算法或一种方法来重写一般情况,因为我找到的唯一答案到目前为止是我发布的一个。

4

5 回答 5

12

这篇 1994 年的论文:Taming Control Flow: A Structured Approach to Elimination Goto Statements提出了一种消除 C 程序中所有 goto 语句的算法。该方法适用于任何用 C# 编写的程序或任何使用常见结构的语言,如 if/switch/loop/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/labels 根除算法结束。

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

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

删除 Goto 语句的正式基础

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

于 2014-09-17T08:37:20.130 回答
2

我有一些尝试采用非结构化程序(在 COBOL 中,不少于)并通过删除 GOTO 的每个实例将其呈现为结构化的实践经验。最初的程序员是经过改造的汇编程序员,尽管他可能知道 PERFORM 语句,但他并没有使用它。是转到转到转到。它是严重的意大利面条代码——价值数百行的意大利面条代码。我花了几个星期的空闲时间试图重写这个可怕的结构,最终我不得不放弃。那是一大堆热气腾腾的疯狂。不过,它奏效了!它的工作是解析以文本格式发送到大型机的用户指令,它做得很好。

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

从那天起,我使用 Modula-2、C、Revelation Basic、三种 VB 和 C# 进行编码,但从未发现需要甚至建议将 GOTO 作为解决方案的情况。然而,对于最初的 BASIC,GOTO 是不可避免的。

于 2014-09-16T20:42:20.177 回答
2

可以避免 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.

我认为 goto 语句看起来好多了。

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

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;
}
于 2014-09-17T09:35:36.150 回答
1

既然你说这是一个理论问题,这里是理论上的答案。

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

不过,Patrice 给出了一个明显更实际的答案,即进行转换的可理解算法。

于 2014-09-17T08:46:00.150 回答
1

我一点也不喜欢我的解决方案。一方面,它非常丑陋,另一方面,它基本上将 goto 包装到一个与 goto 完全相同的开关中。

这就是您在寻找可以替换 goto 的任何使用的通用模式时总是会得到的,它的作用与 goto 完全相同。还能是什么?goto 的不同用途应替换为最佳匹配语言结构。这就是为什么首先存在 switch 语句和 for 循环的构造,以使创建这样的程序流更容易且更不容易出错。编译器仍会生成 goto(或跳转),但它会始终如一地生成我们将搞砸的地方。最重要的是,我们不必阅读编译器生成的内容,但我们可以阅读(和编写)一些更容易理解的内容。

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

于 2014-09-17T10:13:33.637 回答