假设我要编译一些编写不良的C++源代码,其中调用了未定义行为,因此(如他们所说)“任何事情都可能发生”。
从C++语言规范认为符合标准的编译器的角度来看,在这种情况下,“任何事情”是否包括编译器崩溃(或窃取我的密码,或者以其他方式在编译时出现错误或异常行为),或者未定义行为的范围仅限于生成的可执行文件运行时可能发生的事情?
假设我要编译一些编写不良的C++源代码,其中调用了未定义行为,因此(如他们所说)“任何事情都可能发生”。
从C++语言规范认为符合标准的编译器的角度来看,在这种情况下,“任何事情”是否包括编译器崩溃(或窃取我的密码,或者以其他方式在编译时出现错误或异常行为),或者未定义行为的范围仅限于生成的可执行文件运行时可能发生的事情?
我们通常担心的大多数UB,例如NULL-deref或除以零,都是运行时UB。编译会导致运行时UB的函数(如果执行)不应该导致编译器崩溃。除非它可以证明程序一定会执行该函数(和该函数路径)。
(第二个想法:也许我没有考虑到模板/constexpr在编译时需要评估。即使从未调用结果函数,可能允许在翻译期间引起任意怪异行为。)
ISO C++引用中“翻译期间的行为”部分在@StoryTeller的答案中类似于ISO C标准中使用的语言。C不包括模板或constexpr
强制在编译时进行评估。
旧答案,是在我了解翻译时UB之前写的。虽然对于运行时UB是正确的,因此仍然有用。
编译时不存在所谓的UB。它可以在执行路径上对编译器可见,但在C++术语中,直到执行通过函数到达该执行路径之前,它才会发生。
程序中使其无法编译的缺陷不是UB,而是语法错误。这样的程序在C++术语中被称为“不良形式”(如果我理解标准术语正确的话)。一个程序可以是良好形式的,但包含UB。未定义行为和无需诊断消息的不良形式之间的区别
除非我误解了什么,否则 ISO C++ 要求 编译并正确执行此程序,因为执行永远不会触及除以零的操作。(实际上( Godbolt),良好的编译器只会生成有效的可执行文件。即使进行优化,gcc/clang 也会警告x / 0
,但这并不影响程序正常运行。但无论如何,我们正在尝试说明 ISO C++ 允许的实现质量是多么低下。所以检查 gcc/clang 根本不是一个有用的测试,只能用来确认我正确地编写了程序。)
int cause_UB() {
int x=0;
return 1 / x; // UB if ever reached.
// Note I'm avoiding x/0 in case that counts as translation time UB.
// UB still obvious when optimizing across statements, though.
}
int main(){
if (0)
cause_UB();
}
这可能涉及到C预处理器或constexpr
变量以及在这些变量上进行分支,这会导致某些路径上的无意义,这些路径对于这些常量的选择永远不会被触发。
可以假定导致编译时可见UB的执行路径永远不会被采取,例如,x86编译器可以将ud2
(导致非法指令异常)作为cause_UB()
的定义。或者在函数内部,如果if()
的一侧导致了可证明的UB,则可以删除该分支。
但编译器仍然必须以明智和正确的方式编译所有其他内容。所有没有遇到(或不能证明遇到)UB的路径仍必须编译为asm,就好像C++抽象机正在运行它一样。
你可以认为在main
中无条件的编译时可见UB是这个规则的一个例外。或者编译时可以证明从main
开始执行确实会达到保证的UB。
我仍然认为合法的编译器行为包括生成一个只有在运行时才会爆炸的手榴弹。或者更有可能的是,一个由单个非法指令组成的main
定义。我认为如果你从来没有运行过程序,那么就还没有发生任何UB。在我看来,编译器本身不允许爆炸。
包含可能或可证明分支内 UB 的函数
在执行路径上的任何给定位置,UB 都会向后“污染”所有先前的代码。但在实践中,编译器只能在它们可以实际证明执行路径导致编译时可见的 UB 时利用该规则。例如:
int minefield(int x) {
if (x == 3) {
*(char*)nullptr = x/0;
}
return x * 5;
}
i = i ++;
并导致恐龙占领了世界,这不违反标准。但这确实违反了物理定律,因此不会发生 :-)char* p = 1 / 0;
如果编译器遇到 #include "'foo'"
,标准不会对实现的行为施加任何要求。 如果编译器作者判断以运行指定程序并将其输出重定向到临时文件,然后像处理一个#include
那样处理包含文件名中带有单引号的这种形式的include指令将是有用的,那么尝试处理包含上述行的程序可能会运行程序foo
,并导致任何结果。
因此,即使没有努力运行它,试图翻译C程序可能会导致任何可能的后果。