编译器应如何报告错误和警告?


11

我不打算在不久的将来编写编译器。仍然,我对编译器技术以及如何改进这些东西很感兴趣。

从编译语言开始,大多数编译器有两个错误级别:警告和错误,第一个是大多数情况下应修复的非致命性错误,以及表示大多数时间无法生成机器(或字节)的错误。输入的代码。

虽然,这是一个很弱的定义。在某些语言(例如Java)中,如果不使用该@SuppressWarning指令,就无法消除某些警告。而且,Java将某些非致命性问题视为错误(例如,由于我想知道的原因,Java中无法访问的代码会触发错误)。

C#没有相同的问题,但是确实有一些问题。似乎编译是在多个过程中进行的,并且过程失败会阻止进一步的过程继续执行。因此,通常会严重低估构建失败时获得的错误计数。在一次运行中,它可能表明您有两个错误,但是一旦解决它们,您可能会得到26个新错误。

深入研究C和C ++只是显示了Java和C#的编译诊断弱点的不良组合(尽管说Java和C#只是解决了一半的问题可能更准确)。有些警告确实应该是错误的(例如,当并非所有代码路径都返回一个值时),但它们仍然是警告,因为我想,当他们编写标准时,编译器技术还不足以使它们成为此类错误。强制检查。同样,编译器经常检查的内容超出标准所规定的范围,但仍将“标准”警告错误级别用于其他发现。通常,编译器不会立即报告他们可能发现的所有错误;可能需要一些编译才能删除所有这些编译器。更不用说C ++编译器喜欢吐出的隐秘错误,

现在添加了许多构建系统,它们可配置为在编译器发出警告时报告失败,我们只是得到了一个奇怪的组合:并非所有错误都是致命的,但有些警告应该;并非所有警告都是应有的,但有些警告已被明确地压制而没有进一步提及它们的存在;有时所有警告都会变成错误。

非编译语言仍然有糟糕的错误报告。在实际运行代码之前,不会报告Python中的Typos,而且您一次也不会真正引发多个错误,因为脚本在遇到一个错误后便会停止执行。

PHP本身具有许多或多或少的重要错误级别异常。解析错误一次报告一次,警告通常非常严重,以至于中止您的脚本(但默认情况下不是),通知确实经常显示出严重的逻辑问题,某些错误确实还不足以阻止您的脚本,但仍然这样做,并且像往常一样,在PHP中还有一些很奇怪的东西(为什么我们要为不是真正致命的致命错误需要一个错误级别?E_RECOVERABLE_E_ERROR,我是在和您聊天)。

在我看来,我能想到的编译器错误报告的每个实现都被破坏了。真是太遗憾了,因为所有优秀的程序员都坚持认为正确处理错误非常重要,但却无法获得自己的工具来这样做。

您认为什么是报告编译器错误的正确方法?


-1:“非编译语言仍有其糟糕的错误报告份额”主观和争论性的。真的无济于事。这是问题还是投诉?
S.Lott

2
@ S.Lott我认为您在这里有点边缘。我发现我在编译语言方面要困难得多,而且似乎也没有打扰您。
zneak 2011年

@zneak:其他陈述更接近事实,更难以解析。该陈述最容易证明是主观和争论的。
S.Lott

1
@ S.Lott我是否错误地指出Python一次指示一个错误?
zneak 2011年

1
@ S.Lott然后事情一定已经改变了,因为我上次尝试,任何语法错误都将导致Python停止尝试“编译”,并且名称错误将引发异常,并且不检查函数的其余部分(尽管确实离开了报告每个可测试单元一个错误的空间)。我的主观和辩论性陈述是对我认为是事实的介绍,但是如果事实不再如此,我将编辑问题。现在如何运作?
zneak 2011年

Answers:


6

您的问题似乎实际上与我们如何报告编译器错误无关,而是与问题的分类以及如何处理有关。

如果我们暂时假设警告/错误二分法是正确的,那么让我们看看我们可以在此基础上做得如何。一些想法:

  1. 不同的“级别”警告。许多编译器都实现了此目的(例如,GCC有很多用于准确配置将要警告的开关),但是它需要工作-例如,报告所报告警告的严重性以及设置“警告”的能力。是错误”仅适用于指定严重性以上的警告。

  2. Sane错误和警告的分类。仅当代码不符合规范并因此无法编译时,才应报告错误。无法到达的语句,虽然可能是编码错误,但应该是警告,而不是错误-代码仍然是“有效的”,并且在某些合法实例中,人们希望使用无法到达的代码进行编译(例如,对调试进行的快速修改) 。

现在,我不同意您的看法:

  1. 尽力报告所有问题。如果有错误,则会中断构建。构建已损坏。在修复该错误之前,该构建将无法工作。因此,最好立即报告该错误,而不是“继续进行”以便尝试识别出代码中所有“错误”的内容。尤其是当其中很多事情可能都是由初始错误引起的时。

  2. 您的警告应该是错误的特定示例。是的,这可能是程序员的错误。不,它不应该破坏构建。如果我知道函数的输入总是返回值,那么我应该能够运行构建并进行一些测试,而不必添加这些额外的检查。是的,应该警告。那是该死的高等级。但是,除非编译时出现警告错误,否则它不应破坏其内部结构。

有什么想法吗?


我同意您的要求,但我们不同意(duh),因此我的+1。我认为使每个代码路径返回一个值或中止程序都是很容易的,考虑到在发生未定义行为的情况下实际陷入困境有多严重。
zneak 2011年

7

您提出的一个问题是错误报告不完整-例如,报告2个错误,当您修复它们时,您会发现更多错误。

在很大程度上,这是编译器编写程序的折衷方案。根据您所犯的错误,编译器很容易开始严重误解您所做的事情,以至于开始报告与现实关系不大的错误。举例来说,考虑一个简单的错字,其中包含itn x;而不是int x;。除非您做了其他itn有意义的事情,否则这将被报告为错误。这很好,只要它去,但现在考虑接下来会发生什么-编译器会在大量的代码,试图利用 x作为变量。是应该A)停止并让您解决该问题,还是应该B)error: "x": undeclared identifier在该命令上喷出2000错误或发生某些错误?考虑另一种可能性:

int main()[

这是另一个很明显的错别字-显然应该是一个{而不是一个[。编译器可以告诉你那部分很容易地-但它应该然后去汇报的东西这样的错误x=1;说什么样error: statement only allowed inside a function

请注意,这些甚至是相当琐碎的问题-更容易发现更糟糕的问题(特别是,正如我们大多数人所知道的,当您进入C ++模板时)。最重要的是,编译器编写者通常会试图在报告错误错误(即,将某事报告为错误,即使没事的话)与未能报告实际错误之间做出折衷。遵循一些经验法则可以尽量避免在任何一个方向上犯错,但是几乎没有一个法则接近完美。

您提到的另一个问题是Java和@SupressWarning。这与上述情况有很大不同-修复起来很琐碎。它不固定的唯一原因是这样做与Java的基本“特征”不符-即,在他们看来,“这不是bug,而是功能。” 尽管通常这只是个玩笑,但在这种情况下,相关人员被误导了,以至于他们真的相信这是真的。

您在C和C ++中提到的问题是,代码路径没有返回值,这并不是真正允许原始编译器使用的问题。它允许使用数十年的现有代码,其中有些人不想修复,接触甚至阅读。它既古老又丑陋,但是有效,除了它能够继续工作,没有人想要任何东西。不管是好是坏,语言委员会一直坚持保持向后兼容性,因此他们继续允许没人真正喜欢的东西-但有些人(至少认为他们)需要。


3
除了您认为会导致许多其他原因的早期错误的观点之外,还有一个事实是,通常需要构建较晚的通行证才能成功完成较早的通行证。例如,C#编译器的早期检查之一是检查以确保继承图中没有循环-您没有从B继承而从A继承的A。如果您想继续并生成列表在此之后的所有错误中,每一次后续遍历都必须能够应对周期-即使在“良好”编译时,也显着降低了运行速度。
Anon。

@Anon。Java编译器会为尽早通过而做出更大的努力,但我发现它不会明显慢一些。对我来说,这么快就csc放弃有点烦人。
zneak 2011年

@zneak:正如Jerry所说,这是编译器开发人员的折衷方案。编写好的错误诊断程序实际上是一个非常困难的问题(请查看clang,了解您可以实际使用它的距离的示例)。有关 C#编译器的阶段和过程的详细讨论,请参见此处
迪恩·哈丁
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.