【问题标题】:Is it possible to always eliminate goto's?是否有可能总是消除goto?
【发布时间】:2014-09-16 20:02:33
【问题描述】:

虽然 with goto's 很容易(如 f.ex. IL 所证明的那样),但我想知道是否也可以消除 all goto 语句更高级别表达式和语句 - 比如说 - 使用 Java 支持的所有内容。

或者,如果您喜欢:我正在寻找的是“重写规则”,无论 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.开放寻址中“删除”方法的实现(参见:http://en.wikipedia.org/wiki/Open_addressing - 注 1),在特殊情况下使用“goto”非常方便,尽管在这种特殊情况下您可以通过引入“状态”来重写它' 多变的。请注意,这只是一个示例,我已经为 continuation 实现了代码生成器,当您尝试反编译它们时会产生大量的 goto。

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

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

【问题讨论】:

  • goto 是一种 hack,可以提高边缘情况下的性能(从实际的角度来看:标记器、IL 执行引擎和内核)。在任何情况下它都永远不可替代。
  • 您知道whilefor 和其他循环结构在内部使用goto 吗?所以你想避免使用循环?试图避免 switch 语句是没有意义的。好吧,您可以重构开关以使用多态性,但至少应该有一个。
  • @SriramSakthivel 许多语言确实碰巧通过使用跳转语句来实现这些结构,但这不是概念上的要求;还有其他实现这些结构的方法,只是它们在主流编程语言中并不常用。
  • @SriramSakthivel C# 只是代码规范,任何人都可以编写其实现。 C# 语言的 Microsoft 实现恰好具有特定实现细节这一事实并不能使该属性成为 C# 语言本身的事实。
  • @StefandeBruijn goto 本身并不邪恶。它的操作和控制流程缺乏透明度,被认为是“最好避免”。每个while 也可以写成for,但是它们都没有生成真正意大利面条的内在能力,这就是为什么它们都存在方便。 goto 的灵活性也意味着形式化将非常困难,如果不是不可能的话,因为它本质上是对装配级构造的封装。我怀疑你会在那儿完全成功:)

标签: c# goto code-elimination


【解决方案1】:

这篇 1994 年的论文:Taming Control Flow: A Structured Approach to Eliminating Goto Statements 提出了一种算法来消除 C 程序中的所有 goto 语句。该方法适用于任何用 C# 编写的程序或任何使用常见结构的语言,如 if/switch/loop/break/continue(AFAIK,但我不明白为什么它不会)。

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

  • 案例一

    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 根除算法结束。

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

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

A Formal Basis for Removing Goto Statements

A Goto-Elimination Method And Its Implementation For The McCat C Compiler

【讨论】:

  • 谢谢,这正是我一直在寻找的方向!我会阅读这篇论文,看看这是否能回答我的问题。
  • Patrice,这篇论文读起来很棒,绝对是朝着我正在寻找的解决方案迈出的非常好的一步;无论哪种方式,它都很优雅并回答了我的问题。不过,我必须对其进行测试;我不完全确定它将产生什么样的代码(想想 f.ex. 关于逻辑运算符),并且想知道是否需要更多的 AST 优化,例如内联(复制)代码片段,包装辅助函数中的代码等。无论哪种方式,它绝对值得实现。你碰巧知道那上面有什么吗?
  • 我喜欢这篇论文。我想知道 Duff 的设备 (en.wikipedia.org/wiki/Duff%27s_device) 会发生什么转变。
  • @PeterSchneider 这很简单:它不会改变任何东西,因为它只适用于“goto”语句。 :-)
  • @Stefan 不,我不知道有任何实现(当然作者的除外)。如果您开始做某事,请告诉我。
【解决方案2】:

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

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

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

【讨论】:

  • 感谢您的建设性反应。我也一样;我在 1987 年编写了 COMX BASIC,它根本没有任何功能、调用等 - 唯一的选择是过度使用意大利面条 GOTO。 :-) 之后我很少使用它们(开放寻址是少数例外之一)。尽管如此,这表明在我脑海中的某个地方有一个“goto 消除算法”——我在这里尝试做的是将该“算法”形式化。
  • @Stefan 我假设如果要重新编码一些东西,那么在 Basic 中仍然会以不同的方式进行。就像我在学习 C++ 后 C 编程风格发生了变化,倾向于穷人的 OO:一个人会做穷人的控制结构。
  • @PeterSchneider 我只是不认为这只是经验问题 - 如果我可以将 goto 语句重写为 OO 模式,我也很高兴。我个人的经验是,您学习解决特定问题的“代码模式”,用某种语言表示为“最佳实践”。如果您翻译一段代码,您会尝试推断已解决的问题并“插入”解决方案模板。在过去,这些模式中的大多数都包含大量的“goto”语句。问题是:我们如何才能将其形式化,该解决方案是否适用于现有的每个 goto 语句?
  • 所以您认为@Patrice 的文章中提出的转换策略不适用于您的旧程序? (我想他们会的。)手动操作可能很困难,但应该使用编程工具。
  • @PeterSchneider,我没有理由怀疑它们会起作用。尽管评论流似乎是您和 Stefan 之间的对话,但我将这个答案发布到您的评论中,所以我确定您没有向我解决这个问题!
【解决方案3】:

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

【讨论】:

  • 我喜欢你的帖子,但InnerLoop() 的当前版本总是返回 false ,这有悖于它的目的。您可能希望始终返回 condition(并在 InnerLoop() 中声明 amd 将其初始化为 false)。
【解决方案4】:

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

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

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

【讨论】:

  • 严格来说,Java 是图灵完备的事实并不意味着你可以用 Java 表达任何 C# 程序。大象是动物,狮子是动物,但大象不能用鬃毛挥动。用耳朵挥手在实践中是类似的,但不是一回事。
  • @astef 所以如果你想要一个比喻,那就是“如果两个集合 A 和 B 都对自然数有双射投影,那么它们具有相同的“元素数量”,或者更准确地说,相同的基数,即 $\aleph_0$。(哦,MathJax 在这里不起作用,是吗...)它不是一个公共子集,它是一个公共等价。
  • 任何算法 - 是的。您应该能够将任何纯函数移植到任何图灵完备的语言,它会给您相同的结果。除了副作用(执行时间和地点、使用的资源、外部调用等)。问题是一些程序(如果不是大多数程序)有副作用,甚至有时依赖它们。所以,不,你不能用 Java 表达任何 C# 程序。
【解决方案5】:

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

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

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

【讨论】:

    猜你喜欢
    • 2018-10-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多