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。
但是我从评论中感觉到,这更多是非成熟实现的问题。
答案 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修剪可能死代码的路径,并探索他们在优化剩余内容方面的自由。他们并不需要找到所有,大多数甚至任何机会。