为什么c#编译器甚至不警告无穷递归?

本文关键字:递归 警告 编译器 为什么 | 更新日期: 2023-09-27 18:01:36

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

我想:"不,编译器会发现它,不允许它,或者至少发出警告!"

所以我创建了一个简单的应用程序来证明这一点:

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();
    }
}

…但是没有!它编译得很好。为什么编译器不把这段代码标记为有问题呢?如果其中一个按钮被压碎了,你就进入了永无之地!

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

为什么c#编译器甚至不警告无穷递归?

  1. 正如你所说,有时人们想要无限循环。而且。net的jit编译器支持尾部调用优化,所以你甚至可能不会得到无尽递归的堆栈溢出。

  2. 一般情况下,在有限的时间内预测程序是否会在某个点终止或陷入无限循环是不可能的。这被称为停止问题。所有编译器可能找到的都是一些很容易确定的特殊情况。

这不是一个无限循环,而是一个无限递归。这就更糟了,因为它们会导致堆栈溢出。大多数语言都不希望无限递归,除非您正在编写恶意软件。然而,无尽的循环往往是有意为之。服务通常在无限循环中运行。

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

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

假设你写了一个完全合法的方法。突然,因为你从另一个地方调用这个方法,你的编译器抱怨,告诉你你的方法不再合法。我已经在SO上看到了大量的帖子,比如:"我的方法是昨天编译的。今天它不再编译了。但我并没有改变它。"

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

你可以很好地编写一个Main方法,它除了抛出一个Exception之外什么都不做。这是一种更容易发现的模式,也是一种更愚蠢的做法;然而,编译器会很高兴地允许你的程序编译、运行、崩溃和烧录。

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

实际上,在编译时很难弄清楚循环不能在运行时被打破。可能会抛出异常,可能会发生用户交互,可能会在特定线程的某个地方,在您正在监视的端口上改变状态,等等……任何代码分析工具都有太多的可能性来确定,毫无疑问,特定的递归代码段将不可避免地在运行时导致溢出。

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

因为它几乎不可能被检测到!

在你给出的例子中,很明显(对我们来说)代码将永远循环。但是编译器只看到一个函数调用,它并不一定知道是什么调用了这个函数,什么条件逻辑可以改变循环行为等等。

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

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)。你可以得到有限的信息,比如变量没有被分配,而这样做(特别是因为它必须生成符号表),但像这样的高级检测通常留给代码分析工具。

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

在一些情况下,这是需要的行为。例如,基于卡带的游戏主机上的游戏通常在其主循环中没有退出条件,因为程序没有退出的操作系统;该循环将一直运行,直到控制台关机。

老式的穿孔卡片读取单元记录设备一旦卡片处理任务完成就会停止工作,因为硬件不需要继续工作,直到加载新的程序卡堆栈。

相比之下,现代交互式计算机要求计算机不断地监视用户输入或设备活动,因此在某种基本层面上存在一个无限的处理空闲循环,必须持续到设备关闭或重置。例如,在阿波罗制导计算机中,这个外部循环包含在Exec程序中,如果计算机绝对没有其他工作要做,它就会循环运行一个虚拟的工作,这只会关闭"计算机活动"。指示灯。

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