为什么C#编译器甚至没有警告无休止的递归?

时间:2014-05-02 17:21:31

标签: c# .net recursion infinite-loop compiler-warnings

传统应用程序在启动时处于无限循环中;我不知道为什么/如何(代码混淆竞赛候选人),但关于被一遍又一遍地调用的方法(从其他几种方法调用),我想,"我想知道调用它的方法之一是否也调用另一个也调用它的方法?"

我想:"不,编译器能够解决这个问题,而不是允许它,或者至少发出警告!"

所以我创建了一个简单的应用来证明情况就是这样:

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }

    private void button1_Click(object sender, EventArgs e)
    {
        method1();
    }

    private void button2_Click(object sender, EventArgs e)
    {
        method2();
    }

    private void method1()
    {
        MessageBox.Show("method1 called, which will now call method2");
        method2();
    }

    private void method2()
    {
        MessageBox.Show("method2 called, which will now call method1");
        // Note to self: Write an article entitled, "Copy-and-Paste Considered Harmful"
        method1();
    }
}

......但不是!它编译得很好。为什么编译器不会将此代码标记为最可疑?如果任何一个按钮被捣碎,那么你永远不会落地!

好的,有时您可能想要无限循环(起搏器代码等),但我仍然认为应该发出警告。

5 个答案:

答案 0 :(得分:12)

  1. 正如你所说,人们有时会想要无限循环。并且.net的jit-compiler支持tailcall优化,所以你可能甚至不会像你那样得到无限递归的堆栈溢出。

  2. 对于一般情况,在有限时间内预测程序是否会在某个时刻终止或陷入无限循环是不可能的。它被称为halting problem。所有编译器都可能找到一些特殊情况,很容易决定。

答案 1 :(得分:9)

这不是无限循环,而是无休止的递归。这更糟糕,因为它们可能导致堆栈溢出。除非您编写恶意软件,否则大多数语言都不需要无限递归。然而,无尽的循环通常是有意的。服务通常以无限循环运行。

为了检测这种情况,编译器必须按照方法调用来分析代码;但是C#编译器将此过程限制为当前方法中的直接代码。例如,这里可以跟踪未初始化或未使用的变量,并且可以检测不可达的代码。在编译速度和静态分析和优化的深度之间需要权衡。

也很难知道程序员的真实意图。

想象一下,你写了一个完全合法的方法。突然因为你从另一个地方调用这个方法,你的编译器会抱怨并告诉你你的方法不再合法。我已经可以在SO上看到大量帖子了:“我昨天编译的方法。今天它不再编译了。但我没有改变它。”

答案 2 :(得分:7)

简单地说:编译器的编码模式不是编译器的工作

你可以写一个Main方法,除了抛出Exception之外什么都不做。这是一个更容易检测的模式,也是一个更愚蠢的事情;但编译器很乐意允许您的程序编译,运行,崩溃和刻录。

话虽如此,从技术上讲,就编译器而言,无限循环/递归是完全合法的,因此没有理由抱怨它。

实际上,在编译时很难弄清楚循环在运行时是否会被破坏。可能会抛出异常,可能会发生用户交互,状态可能会在特定线程的某个位置,正在监视的端口上发生变化等等......在那里建立任何代码分析工具的可能性太大了毫无疑问,特定的递归代码段将不可避免地在运行时导致溢出。

我认为防止这些情况的正确方法是通过单元测试组织。您在测试中覆盖的代码路径越多,您遇到这种情况的可能性就越小。

答案 3 :(得分:3)

因为它几乎不可能被发现!

在您给出的示例中,很明显(对我们而言)代码将永远循环。但是编译器只看到一个函数调用,它当时不一定知道调用该函数是什么,条件逻辑可以改变循环行为等。

例如,通过这种微小的改变,你不再处于无限循环中:

private bool method1called = false;
private void method1()
{
    MessageBox.Show("method1 called, which will now call method2");

    if (!method1called)
       method2();

    method1called = true;
}

private void method2()
{
    MessageBox.Show("method2 called, which will now call method1");
    method1();
}

如果没有实际运行程序,你怎么知道它没有循环?我可能会看到while (true)的警告,但是有足够的有效用例,没有警告它也是有意义的。

编译器只是解析代码并转换为IL(无论如何都适用于.NET)。您可以获得有限的信息,例如在执行此操作时未分配的变量(特别是因为它必须生成符号表),但这样的高级检测通常留给代码分析工具。

答案 4 :(得分:2)

我在这里找到的无限循环维基上找到了这个:http://en.wikipedia.org/wiki/Infinite_loop#Intentional_looping

  

在某些情况下,这是理想的行为。例如,基于盒式游戏机的游戏通常在其主循环中没有退出条件,因为没有用于程序退出的操作系统;循环运行直到控制台断电。

     

一旦卡处理任务完成,古董穿插卡读取单元记录设备就会停止,因为在加载新的程序卡堆栈之前,不需要硬件继续操作。

     

相比之下,现代交互式计算机要求计算机不断监视用户输入或设备活动,因此在某些基本级别上存在无限处理空闲循环,必须持续到设备关闭或重置为止。例如,在Apollo Guidance计算机中,这个外部循环包含在Exec程序中,如果计算机完全没有其他工作要做,它将循环运行一个虚拟作业,只需关闭计算机活动&# 34;指示灯。

     

现代计算机在崩溃时通常也不会停止处理器或主板电路驱动时钟。相反,它们会回退到向操作员显示消息的错误状态,并进入无限循环,等待用户响应提示继续或重置设备。

希望这有帮助。