为什么不';这个明显的无限递归不会给编译器一个警告

本文关键字:编译器 警告 一个 无限 为什么不 递归 | 更新日期: 2023-09-27 18:19:34

几个月前,我不得不修复一些导致一些问题的代码。代码看起来基本上是这样的:

int badFun() { return badFun(); }

这显然导致了堆栈溢出,即使在我使用的高级语言中也是如此(SilkTest中的4Test)。这段代码不可能被视为有益的。问题的第一个迹象是在脚本完成后看到警告,但没有编译错误或警告。奇怪的是,我尝试用C++、C#和Python编写具有相同结构的程序,并且所有程序的编译/解释都没有语法错误或警告,即使在所有情况下都存在运行时错误。在这些案例中,我甚至没有看到任何警告。为什么默认情况下这不被视为一个可能的问题?

编辑:我试着用这三种语言编写该函数的等价物,所以我添加了这些函数标记。我更感兴趣的是这样的代码在没有警告的情况下通过的总体原因。如有必要,请重新标记。

为什么不';这个明显的无限递归不会给编译器一个警告

问题是:编译器警告是功能。特征需要努力,而努力是一个有限的量。(它可能以美元衡量,也可能以某人愿意为开源项目投入的小时数衡量,但我向你保证,它是有限的。)

因此,我们必须为这项工作编列预算。我们花在设计、实现、测试和调试一个功能上的每一个小时,都是我们本可以做其他事情的一个小时。因此,我们在决定添加哪些功能时非常谨慎。

所有功能都是如此。警告还有特别的附加问题。警告必须是关于具有以下特征的代码:

  • 合法。显然,该准则必须是合法的;如果它不合法,那么它一开始就不是一个警告,而是一个错误
  • 几乎可以肯定是错的。警告您正确、理想的代码的警告是错误的警告。(此外,如果代码正确的,那么应该有一种方法来编写代码,使警告消失。)
  • 不明显。警告应该告诉你一些微妙的错误,而不是显而易见的错误
  • 可进行分析。有些警告根本不可能;例如,要求编译器解决"停止问题"的警告不会发生,因为这是不可能的
  • 不太可能被其他形式的测试抓住

在你的具体例子中,我们看到其中一些条件得到了满足。该准则是合法的,几乎可以肯定是错误的。但这是显而易见的吗?有人可以很容易地查看代码,发现它是一个无限递归;这个警告没有多大帮助。它可以分析吗?你给出的一个微不足道的例子是,但寻找无界递归的一般问题等价于求解停顿问题。它不太可能被其他形式的测试抓住吗?不。当你在测试用例中运行代码时,你会得到一个异常,告诉你到底出了什么问题。

因此,我们发出这种警告是不值得的。我们可以用更好的方式来支出这笔预算。

为什么默认情况下这不被视为问题?

该错误是运行时错误,而不是编译时错误。代码是完全有效的,它只是做了一些愚蠢的事情。你展示的非常简单的情况当然可以被检测到,但许多稍微复杂一点的情况很难被检测到:

void evil() {
    if (somethingThatTurnsOutToAlwaysBeTrue)
        evil();
}

为了确定这是否是一个问题,编译器必须尝试弄清楚这个条件是否总是真的。在一般情况下,我不认为这比确定程序最终是否会停止(即,它是可证明的不可计算的)更容易计算。

任何编程语言的编译器都不知道所编译代码的语义。这是有效的代码,尽管很愚蠢,所以它将被编译。

编译器或解释器应该如何知道函数在做什么?编译器和解释器的作用域是编译或解释语法代码,而不是解释代码的语义。

即使编译器确实检查了这一点,你在哪里划线?如果你有一个递归函数,永远计算阶乘,会怎么样?

因为编译器不会检查这些东西。

如果你在Visual Studio中安装了像Resharper这样的代码分析器,如果你启用了代码分析选项,它会带来无限递归调用或类似的警告。

我怀疑编译器能否在编译时检测到运行时现象(堆栈溢出)。有很多有效的情况可以调用函数内部的递归。但是,编译器如何才能区分递归的好坏呢?

除非它添加了一些人工智能,否则我认为编译器无法检测好递归和坏递归之间的差异,这是程序员的工作。

正如您所提到的,编译器只是检查语法错误。递归函数完全有效,没有任何错误。

运行时,

当堆栈溢出时,它会抛出一个错误,原因是堆栈溢出*而不是代码*。

递归函数完全有效,但在实现中,我们需要在填充堆栈之前对返回值进行条件检查。