编译器如何知道C ++ constexpr计算不会触发未定义的行为?

时间:2019-10-30 23:41:32

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

C ++标准授权编译器在C ++ constexpr计算中检查未定义的行为

this talk中,钱德勒·卡鲁斯(Chandler Carruth)指出,在检查UB时“您将耗尽检测错误的能力”,并且通常情况下,检测UB与halting problem有关,因此证明无法决定

他不是在constexpr中谈论UB,但是 constexpr的计算与C ++ 14以来的常规程序一样普遍,因此仍然适用。

当编译器无法确定程序是否为UB时,该怎么办??他们仍然接受该程序并继续用手指继续编译吗?还是他们更保守并拒绝了该计划,即使它可能是正确的? (我个人的感觉是他们这样做了)

对我来说,这具有实际意义,因为我有一个constexpr评估,其中使用Clang可以很好地编译非平凡的指针算法,但不能使用GCC编译,而且我很确定这不是UB。您可以说这是一个GCC错误,但是如果UB无法确定,那么所有编译器在这方面都会是错误的。

从根本上说,为什么标准要求无UB ?有技术原因吗?或更多是一种哲学性的想法(“如果编译器无法检查,则程序员可以触发UB,并且会导致不良后果”)?

我认为这与C ++的其余部分不一致,后者永远不会阻止您用脚射击。我希望GCC接受我的constexpr代码并崩溃,或者如果UB发出垃圾;而不是在不知道它是否为UB时不进行编译。

======= 编辑 ======

正如M.M和Nicol Bolas所指出的,该标准规定了限制(即使在C ++ 14中也是如此),因此我们永远不会陷入停滞的UB问题类型。但是,我仍然想知道是否检查UB是否太复杂,并且如果编译器试探法失败了,那么他们(可能错误地)将其标记为非constexpr。

但是我从评论中感觉到,这更多是非成熟实现的问题。

2 个答案:

答案 0 :(得分:4)

  

在这次谈话中,钱德勒·卡鲁斯(Chandler Carruth)指出,在检查UB时“您将耗尽检测错误的能力”,并且在通常情况下,检测UB与暂停问题有关,因此证明无法决定。

暂停问题是当您采用某个程序并尝试确定该程序(如果要执行)是否肯定会暂停时。根据定义,“暂停问题”仅将程序视为锁定对象。

恒定评估是... 评估。您正在执行程序,而不仅仅是查看源代码。

当程序的执行执行未定义的操作时,会发生未定义的行为。 UB的大多数情况不能仅仅通过检查源代码来确定是否定义良好。考虑以下代码:

void foo(void *ptr)
{
  *reinterpret_cast<int*>(ptr) = 20;
}

是那个UB吗?这取决于;如果将指向int的指针传递到foo中,则它的定义是明确的。该代码是否定义明确,只能通过执行的方式来确定。

持续评估需要执行代码;这就是为什么我们经常将其称为编译时执行。当您执行代码时,可以知道是否已将foo的特定执行传递给了指向实际int的指针(忽略reinterpret_cast被禁止的事实1}}代码)。这样,在评估时,您可以知道UB是否正在发生。

  

当编译器无法确定程序是否为UB时,该怎么办?

实际上这不是可能发生的事情。假设规范是完整的且没有漏洞,程序的 execution 是否表现出良好定义的行为仅仅是遵循规范的问题。

GCC与Clang的关系并不是因为无法确定UB。

  

从根本上来说,为什么标准要求无UB?有技术原因吗?

假设地,我们可以从C ++甚至C语言中剔除所有未定义的行为。我们可以将所有事情都定义为先验的,并从该语言中删除其评估不能从第一原理中绝对确定的任何内容。

该标准不会这样做,因为那很不好。这将阻止我们做各种有用的低级工作。这将阻止有用的编译器优化。依此类推。

所有这些原因均不适用于编译时代码执行。特别是整个“有用的,低级的东西”部分。对于已编译的代码,有一个实际的真实机器在其上执行。因此,拥有与真实机器对话的后门很有意义。但是,在编译时,没有真正的机器可以交谈。只有C ++定义的抽象机。那么允许UB的意义何在?

编译器不会生成机器语言并执行它。常量评估基本上是在编译器中执行脚本语言。与大多数脚本语言一样,您希望它能够安全正确地进行评估。您希望迅速发现错误(并且UB 是错误 ),并在出现故障时提供清晰的错误消息,而不是在过程的后期任意死亡。 / p>

答案 1 :(得分:3)

您缺少的一点是常量表达式仅允许使用该语言的受限子集。

如果不这样做,您将不再有一个常量表达式,并且如果您所处的环境中需要一个常量,则必须使用标准命令来诊断错误。

constexpr函数只要是常量表达式,就至少必须具有一个输入,而无需诊断。其余所有可能都不是。

在一般情况下,编译器仅记录导致UB修剪可能死代码的路径,并探索他们在优化剩余内容方面的自由。他们并不需要找到所有,大多数甚至任何机会。