使用未定义的行为是否未定义编译器的行为?

时间:2015-08-22 10:04:16

标签: c++ language-lawyer undefined-behavior

当我answered这个question时,我写道:

  

首先,需要注意的是,不仅未定义用户程序的行为,而且未定义编译器的行为。

但是有disagreement in a comment,所以我想在这里提出问题:

如果源代码包含未定义的行为,是否只是未定义的已翻译机器代码的行为,或者编译器的行为是否也未定义?

标准定义了抽象机器的行为(1.9):

  

本国际标准中的语义描述定义了参数化的非确定性摘要   机。本国际标准对符合实施的结构没有要求。   特别是,它们不需要复制或模拟抽象机器的结构。相反,符合   如下所述,实现需要模拟(仅)抽象机器的可观察行为   下方。

也许问题是编译器是否是该机器的一部分,如果是,是否允许该部件以未定义的方式运行?

这个问题的一个更实际的版本是:
假设编译器在所有控制路径上找到UB时会崩溃或不产生任何输出,如此程序中所示:

int main() {
    complex_things_without_UB();
    int x = 42;
    x = x++;  //UB here
    return x;
}

但是否则会产生正确的二进制文件。这仍然是符合标准的编译器吗?

7 个答案:

答案 0 :(得分:6)

C ++标准定义了代码的行为,它没有定义编译器的行为。因此,引用编译器的未定义行为并没有多大意义 - 从一开始就没有明确定义。唯一的要求是它产生符合代码标准指南的实现。 如何它是一个实现细节。

答案 1 :(得分:3)

这是一个非常模糊的整体线条。关键是源代码没有定义的行为,这意味着生成的代码的行为没有很好地定义。

编译器应该按照某种定义的方式运行 - 当然,这可能是相当“随机”的(例如,编译器可能会选择在计算中插入一个随机数 - 或者甚至调用{{1 - 它仍然完全在编译器的权限范围内)。在某些情况下,编译器(ab)会使用以下事实:它知道某些内容未定义以进行优化。

我认为这是一个非常糟糕的编译器实现,例如,如果编译器崩溃或导致硬盘格式化,但我相信编译器可能仍然是“正确的”,如果它说“这是未定义的,我拒绝编译“[以某种方式]。

当然,(很多)情况都是未定义的,不是因为构造本身是未定义的,而是因为“很难定义可以在很多地方实现的单一行为” - 例如,使用无效指针(rand或use-after-free)是未定义的,但编译器可能不知道并理解它是否正确。相反,它取决于处理器体系结构,如果您在随机地址处使用指针,或者在释放它之后会发生什么。这两种情况都可能导致一台机器崩溃,而不是崩溃,但是另一台机器会出现错误结果,在某些情况下“你不会注意到任何问题”。这显然不是编译器未定义的行为,而是结果程序。

答案 2 :(得分:2)

  

是否只有未定义的已翻译机器代码的行为,或者编译器的行为是否也未定义?

ISO C和C ++描述了C和C ++程序的外观。它们没有描述它们运行的​​环境。我们通常使用术语编译器来引用将C和C ++转换为机器代码的工具;但是,正式使用的术语是实现,它肯定更宽。

因此,唯一未定义的行为是程序之一。这也是UB的定义:

  

未定义的行为
  使用不可移植或错误的行为    程序 构造或错误数据,本国际   标准没有要求

答案 3 :(得分:2)

如果代码具有未定义的行为,则意味着标准不知道如何处理这样的事情。因此它可以提供任何输出。我认为它与编译器没有关系,因为它没有意义。有意义的是它必须是符合标准的实现

因此,如果标准不知道如何处理此类代码,那么编译器如何提供定义的输出

答案 4 :(得分:1)

假设"编译器的未定义行为"意味着"对生成的可执行程序的行为没有要求"然后,当提供包含未定义行为结构的源代码时,编译器的行为是未定义的。

将此与具有正确源代码的编译器的行为进行比较。所有符合标准的编译器必须生成具有等效行为的可执行代码,该标准由正确源代码的标准定义。

答案 5 :(得分:1)

我自己认为“未定义行为”中的行为实现的行为。规范指的是我们可能与编译等同的“翻译”过程,但是你可以将程序编译成可执行代码的事实在这里是不相关的,结果仍然被认为是实现的一部分,至少在就行为而言。请注意,尽管规范确实定义了C程序的行为方式,但是当它将需求放在实现上时,程序的行为也可以被视为实现的一个要求(或一组要求)。

在任何情况下,未定义的行为当然可以引用编译器的行为。参见C11 3.4.3中的注释:

  

可能的未定义行为包括完全忽略具有不可预测结果的情况,在转换或程序执行期间以环境特征(有或没有发出诊断消息)的文档方式执行,终止翻译或执行(发布诊断信息)。

“终止翻译”明确指的是编译失败,而“终止执行”明确指的是正在运行的程序的行为。

另见附录J.2,其中列出了未定义行为的示例。其中的例子有:

  

非空源文件不以新行字符结尾,该字符不会立即以反斜杠字符开头,或以部分预处理标记或注释结尾(5.1.1.2)

这应该在执行时而不是在翻译时导致未定义的行为,这似乎很荒谬。还有其他各种类似的例子。整个集清楚地显示了在编译时和运行时都可能发生未定义行为的情况。

答案 6 :(得分:0)

标准中没有提到编译器,实现细节由供应商决定。

标准定义 代码应如何表现(以语法和语义方式)和/或在关于某些标准库算法的复杂性术语中受到限制。源代码不必具有精确的行为(也不是在任何地方定义)。每个编译器都必须生成在 as-if 规则下正确的代码。

引用编译器的未定义行为

是没有意义的