为什么这种明显的无限递归没有给出编译器警告?
Why doesn't this obvious infinite recursion give a compiler warning?
几个月前,我不得不修复一些导致一些问题的代码。代码看起来基本上是这样的:
int badFun() { return badFun(); }
这显然导致了堆栈溢出,即使在我使用的高级语言中也是如此(SilkTest中的4Test)。这段代码不可能被视为有益的。问题的第一个迹象是在脚本完成后看到警告,但没有编译错误或警告。奇怪的是,我尝试用C++、C#和Python编写具有相同结构的程序,并且所有程序的编译/解释都没有语法错误或警告,即使在所有情况下都存在运行时错误。在这些案例中,我甚至没有看到任何警告。为什么默认情况下这不被视为一个可能的问题?
编辑:我试着用这三种语言编写该函数的等价物,所以我添加了这些函数标记。我更感兴趣的是这样的代码在没有警告的情况下通过的总体原因。如有必要,请重新标记。
问题是:编译器警告是功能。特征需要努力,而努力是一个有限的量。(它可能以美元衡量,也可能以某人愿意为开源项目投入的小时数衡量,但我向你保证,它是有限的。)
因此,我们必须为这项工作编列预算。我们花在设计、实现、测试和调试一个功能上的每一个小时,都是我们本可以做其他事情的一个小时。因此,我们在决定添加哪些功能时非常谨慎。
所有功能都是如此。警告还有特别的附加问题。警告必须是关于具有以下特征的代码:
- 合法。显然,该准则必须是合法的;如果它不合法,那么它一开始就不是一个警告,而是一个错误
- 几乎可以肯定是错的。警告您正确、理想的代码的警告是错误的警告。(此外,如果代码是正确的,那么应该有一种方法来编写代码,使警告消失。)
- 不明显。警告应该告诉你一些微妙的错误,而不是显而易见的错误
- 可进行分析。有些警告根本不可能;例如,要求编译器解决"停止问题"的警告不会发生,因为这是不可能的
- 不太可能被其他形式的测试抓住
在你的具体例子中,我们看到其中一些条件得到了满足。该准则是合法的,几乎可以肯定是错误的。但这是显而易见的吗?有人可以很容易地查看代码,发现它是一个无限递归;这个警告没有多大帮助。它可以分析吗?你给出的一个微不足道的例子是,但寻找无界递归的一般问题等价于求解停顿问题。它不太可能被其他形式的测试抓住吗?不。当你在测试用例中运行代码时,你会得到一个异常,告诉你到底出了什么问题。
因此,我们发出这种警告是不值得的。我们可以用更好的方式来支出这笔预算。
为什么默认情况下这不被视为问题?
该错误是运行时错误,而不是编译时错误。代码是完全有效的,它只是做了一些愚蠢的事情。你展示的非常简单的情况当然可以被检测到,但许多稍微复杂一点的情况很难被检测到:
void evil() {
if (somethingThatTurnsOutToAlwaysBeTrue)
evil();
}
为了确定这是否是一个问题,编译器必须尝试弄清楚这个条件是否总是真的。在一般情况下,我不认为这比确定程序最终是否会停止(即,它是可证明的不可计算的)更容易计算。
任何编程语言的编译器都不知道所编译代码的语义。这是有效的代码,尽管很愚蠢,所以它将被编译。
编译器或解释器应该如何知道函数在做什么?编译器和解释器的作用域是编译或解释语法代码,而不是解释代码的语义。
即使编译器确实检查了这一点,你在哪里划线?如果你有一个递归函数,永远计算阶乘,会怎么样?
因为编译器不会检查这些东西。
如果你在Visual Studio
中安装了像Resharper
这样的代码分析器,如果你启用了代码分析选项,它会带来无限递归调用或类似的警告。
我怀疑编译器能否在编译时检测到运行时现象(堆栈溢出)。有很多有效的情况可以调用函数内部的递归。但是,编译器如何才能区分递归的好坏呢?
除非它添加了一些人工智能,否则我认为编译器无法检测好递归和坏递归之间的差异,这是程序员的工作。
正如您所提到的,编译器只是检查语法错误。递归函数完全有效,没有任何错误。
运行时,
当堆栈溢出时,它会抛出一个错误,原因是堆栈溢出*而不是代码*。
递归函数完全有效,但在实现中,我们需要在填充堆栈之前对返回值进行条件检查。
- 如何修复编译器警告 C6386 和 C6385?
- 编译器警告:执行到达值返回函数的末尾而不返回值
- 为什么布尔开关语句有编译器警告?
- 使用 reverse_iterator 而不是const_reverse_iterator并获得讨厌的编译器警告和错误
- 来自 std::chrono 的编译器警告,但未被使用
- 警告级别为 3 的 int 的 std::vector push_back 处的编译器警告
- 有没有办法在从临时返回按值string_view时获得编译器警告?
- 常量更改而不const_cast<> 为什么没有编译器警告/错误?
- C++ 添加编译器警告,以错误使用自定义打印/日志功能
- 我正在尝试在我的类中创建一个静态成员,但编译器警告我它是未定义的
- 是否可以将移动的变量标记为不再可用,并在使用它时收到编译器警告?
- 为什么我应该始终启用编译器警告
- 对列表类中的泛型方法禁用编译器警告 2100,该泛型方法可能包含指针,也可能不包含指针
- C++ 如何禁用具有不同符号变量比较的特定行的编译器警告?
- CMake:如何将每个资源编译器警告视为错误并抑制特定警告?
- 为什么方法重载或枚举标志定义会触发 gcc7.2 编译器警告?
- 违反严格别名并不总是会产生编译器警告
- (如何)获取有关某个 C++Year 的已弃用/不推荐的功能/构造的编译器警告
- 使用 VS 2015 的编译器警告 4456
- 在嵌套名称说明符中使用枚举(编译器警告)