超出翻译限制是未定义行为吗?有没有检查工具可以发现它?

13

原问题:

我正在查找C90标准,以了解在编写高度可移植代码时需要注意的事项,同时对编译器供应商的善意持有低信任度,并假设如果我做错了事情,我的软件可能会有时候导致某些人死亡。可以说我有点偏执。

目前,我正在考虑“翻译限制”(5.2.4.1 ANSI / ISO 9899:1990)。正如标准和“ansi C是否对程序中外部变量的数量设置限制?”中指出的那样,这些是标准符合实现的最低要求。另一方面,这意味着任何实现都不必做更多 - 如果我想确保我的代码适用于任何符合实现,这些限制对我来说代表了绝对限制。

到目前为止,非常令人恼火。

因此,编译器供应商选择等于或高于最低所需的翻译限制。

如果超出特定实现的这些实现定义的翻译限制会发生什么?在我拥有的 ANSI/IO 9899:1990 (C90) 副本中,我没有找到任何东西,因此我认为这是“第三种”的未定义行为(通过省略)。另一方面,这不会是我第一次误解标准或找不到正确的段落。

所以这里是我的问题:

  • 在 C90 中超出特定实现的翻译限制是否是未定义的行为?

  • C90 行为适用于经过更正的版本直到 C95/C96 和新的迭代 C99 & C11 吗?

  • 有人看到过检查最小限制或(工具)用户定义限制的检查器工具吗?

原问题之外的方面:

答案和评论中的有趣方面:

1)正如Michael Burr在对该问题的直接评论中指出的那样,根据C标准(我仅检查过未经修订的C90和C99草案,Michael提到的这里),符合C实现只需要接受一个包含所有限制的程序,这在最严格的解释下使任何最小限制保证都失去了作用。

2) 正如rubenvbKeith Thompson所指出的,一些高质量的实现应该为这种情况提供诊断,即当其定义的限制超过时,特别是如果不符合最低要求(rubenvb在comment中提供了MSVC的示例)。

3) 虽然超出编译器限制可能会导致未定义行为,但肯定会导致某些错误,对于我代码中适用于某个特定片段的“变量”的值,它们受到翻译限制的影响,代表着重复使用的前提条件。

我的个人策略

1) 因此,为了最大程度的谨慎,我将让自己成为傻瓜,并通过请求编译器供应商保证,实现选择的限制适用于任何程序。 :-(

2) 所以我将调查编译器文档和编译器支持的容错能力,以获得确认: - 对于每个翻译限制,如果超过,则会引发诊断,并且 - 因为它是未定义行为,所以如果超出翻译限制的每个实例都会引发诊断-否则另一个错误已经防止了编译。

3) 所以我将尝试获取一个工具(如果必须开发自己),该工具可以测量这些值,并将它们作为重用代码的先决条件。正如Keith Thompson在这个answer中指出的那样,一些值可能需要更深入地了解实现方式。我不确定在这种情况下除了2)中的操作之外还有什么帮助,但据我所见,我必须进行测试-但我只需要测试是否存在未定义行为(没有诊断),如果是这种情况,则成功的测试不能保证正确性在一般情况下成立。

已回答:

是的,这是通过忽略而产生的未定义行为。

Keith Thompson在他的(被接受的)anwser中,使用C标准文档的术语和参考资料表明这是未定义行为。

代码中检查交易限制的工具尚未被评论者发现。如果有任何人拥有(即使是部分)此功能的工具,请留下答案或评论。


据我所知,对于未指定的行为,标准明确要告诉你哪些方面是未指定的。如果我理解有误,请纠正我 - 尤其是,我手头只有旧的C90标准。 - Mark A.
1
请注意,严格来说(因为您似乎对非常严格的解释感兴趣),即使符合标准中最小翻译限制的实现也不一定总是必须满足这些最小限制。标准规定,实现只需针对一个程序满足这些限制(全部限制)。当然,意图是实现应该能够满足任何程序的这些最小限制。但根据标准,这并非必需。 - Michael Burr
@MarkA.:你可以在这里免费获取带有TC集成的C99副本:http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1256.pdf - Michael Burr
@Michael Burr。是的-我也为这个句子苦恼过。我得出结论,这(连同我怀疑的UB)将使标准和编译器完全无用。因此,我假设“任何”解释。感谢您指出这一点。感谢您提供的链接。无论如何,我被迫在生产代码中使用C90,要求严格遵守ANSI C90。但我希望这很快会改变-因此C99标准将对我有很大帮助。 - Mark A.
请不要像之前那样修改答案,人们可以通过下面的选中答案来阅读答案,也请不要延长您的问题。相反,请提出一个新问题(或两个问题),并参考此问题。 - rubenvb
显示剩余5条评论
3个回答

3
我认为这种行为是未定义的。
标准要求对于违反约束或语法规则的任何翻译单元进行诊断(N1570 5.1.1.3),并且可能无法成功翻译包含在预处理阶段中幸存的#error指令的翻译单元(n1570 4,第4段)。 (N1570是C11标准的草案,但这在C90、C99和C11中都是相同的,只是C99添加了#error。)
所有约束和语法规则都在标准中明确指定。超过实现定义的限制既不违反约束也不违反语法规则。我认为很明显,如果一个程序超过了翻译限制,实现不需要成功处理它,但标准没有说明它应该如何响应这样的违规行为。因此,由于遗漏而导致的行为是未定义的。
(高质量的实现将发出诊断,说已超出限制,但标准不要求这样做。)
回答你问题的第三部分,我没有听说过一种静态检查工具,可以检查程序是否违反了最小翻译限制。这样的工具可能非常有用,一旦你拥有了C语言解析器,编写起来可能并不太困难。对于对象大小的限制(在C90中为32767字节,在C99和C11中为65535字节),它必须知道编译器如何确定对象大小;例如int arr[30000];可能超过65535字节,具体取决于sizeof(int)。如果有人已经实现了这样的工具,而我只是没有听说过,我不会感到太惊讶。
请注意,大多数实现并没有强制执行标准允许的固定限制;相反,任何限制都是由编译时可用的内存资源所规定的。
标准以一种相当奇怪的方式呈现了翻译限制。我特别考虑到以下条款:

实现应该能够翻译和执行至少包含以下每个限制中至少一个实例的程序:

(这是C90、C99和C11中的第5.2.4.1节)。因此,一种歪曲的实现可以接受恰好一个程序并拒绝所有其他程序。
我认为的重点在于指定所有实现必须满足的合理限制是不切实际的。标准可以规定所有实现必须始终接受至少32767字节的对象,但是如果一个程序定义了一百万个这样的对象呢?限制以极其复杂的方式相互作用,并且相互作用的性质取决于每个编译器的内部结构。(如果你认为你可以更好地定义翻译限制的要求,我鼓励你尝试一下。)
相反,标准以这样的方式规定要求,即遵守标准的“字面”要求实现有用的编译器的最简单方法是遵守标准的“精神”,即不施加任何不合理的限制。符合标准的无用编译器是可能的,但是无关紧要; 我不知道是否有人曾经实现过这样的东西,我确信没有人会尝试使用它。

感谢您的确认。我完全同意您的论点。但在我提出问题之前,我就已经这样做了。因此,我将让它保持开放状态几天,看看是否有人想提出不同的观点。至于诊断,对于某些多方参与的项目,我可能对C实现的选择影响较小。您听说过一种静态检查器工具,用于检查翻译限制吗? - Mark A.
谢谢您的更新。是的,我已经阅读了我们编译器中那些“基于内存”的更新。当然,这对于实现非常高兼容性的代码并没有太大帮助。另一方面,如果我手头有一个检查器和/或在开发过程中验证编译器行为(并作为重用的前提条件),我可能会对这个未定义行为放松一些。 :-) - Mark A.
感谢您的进一步更新。我完全同意,只遵循字面意思的编译器是不会被使用的。而且我肯定不会说(至少在未来几年内)我能比标准规定更好地指定任何内容。(也许除了“非空文件不以新行结尾的UB问题…”)。但是我将会 - 并且认为 - 在标准的解释方面必须要严谨,特别是当涉及到UB时,因为我最多只是一个普通的C程序员,而UB可能会导致任何测试程序无法发现我的(功能性)错误。 :-) - Mark A.
优秀的答案,非常精准地解释了标准文本(以及通常被误解的法律文本)中一个常见的问题:书面文字的“字面”和“精神”(或法律术语中的“原则”)之间的区别。顺便提一下,ISO C++包括一个在ISO C中找不到的声明:“然而,这些数量仅是指导方针,不确定是否符合规定。” - alecov
1
@Alek:C标准从未旨在完全描述某个平台上实现良好的C实现所必须执行的所有操作,但现代编译器编写者已经忽视了这一点,表现出的行为虽然是允许的,但几乎可以肯定地被标准的作者视为难以理解地降低其实现质量。 - supercat

0
在某些环境中,应用程序可能会接收到等于可用内存总量减去代码和静态数据组合大小的堆栈空间。如果直到运行程序才能确定可用内存的数量,那么编译器、链接器或任何其他类似工具是否足够无法知道。标准中没有任何要求,规定当尝试运行程序时,如果没有足够的内存来处理其堆栈需求,必须发生什么事情。
如果标准提供了一种方式,使程序在任何可用内存下运行时都能确保一定程度的可预测性,那将是有帮助的,但目前它并没有这样做。在许多平台上,将有一些可用内存,它足够大,以至于操作系统加载程序时不会拒绝可执行文件,但仍然足够小,以至于应用程序在启动后几乎立即遭受堆栈溢出。C标准的作者不想声明C不能与这些平台一起使用,但他们也不能真正说明在尝试使用该关键内存量运行代码时平台会发生什么。

0
这不是未定义行为,而是实现定义行为。这意味着一切都取决于编译器。
是的,最小实现指南保持相同或针对新标准版本进行了扩展。
你可能可以使用Clang来做到这一点,但是你需要使用Clang API自己编写工具,我不知道是否有现成的实现。
无论如何:标准并没有设定限制,“它们更像是指南”,(实际上只是指南)。你需要检查编译器是否有任何限制,以构建代码,这是唯一的方法,而不是仅仅在某人的鼻子前挥舞标准文档。由于MSVC的实现特别糟糕,我甚至敢说,如果它编译您的代码(假设代码本身没有非法结构),那么您就很安全了。

谢谢您的快速回答。我可以看出,限制本身明显是实现定义的。但我看不到编译器在超出限制时需要呈现诊断或任何指定行为的要求。您能否向我展示标准中的这一段落? - Mark A.
@MarkA。诊断也是实现定义的,我想...至少对于MSVC(我遇到任何限制的唯一一个),您会得到描述性错误消息,其中告诉您为什么它很糟糕。 - rubenvb
@MarkA。MSVC的限制在此处有文档记录(http://msdn.microsoft.com/en-us/library/ft39hh4x.aspx)(虽然有很多C++,但其中也有一些C的限制)。现在看来这些只是非标准限制,而您并不真正需要它们...我会编辑我的答案。 - rubenvb
1
你如何得出行为是实现定义的结论?要使其成为实现定义,标准中必须有一个明确的要求,要求每个实现都记录其对超过翻译限制的翻译单元做出响应的行为方式(请参见C11 / N1570中“实现定义行为”的定义3.4.1)。你在哪里看到这样的要求? - Keith Thompson
1
@rubenvb:是的,许多限制本身是实现定义的,因此必须进行记录。问题在于当超出这些限制时,实现的行为如何。 - Keith Thompson
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接