C代码产生未定义的结果,编译器生成无效的代码(使用-O3)

11

我知道在C程序中执行某些操作时,结果是未定义的。但是,编译器不应该生成无效(机器)代码,对吗?如果代码做错了事或生成段错误之类的东西,那倒是合理的...

这是否符合编译器规范,还是编译器的一个漏洞?

这是我使用的(简单的)程序:

int main() {
    char *ptr = 0;
    *(ptr) = 0;
}

我正在使用-O3进行编译。但这不会生成无效的硬件指令,对吧?如果使用-O0,运行代码时我会遇到段错误。那样似乎更加合理。

编辑:它正在生成一个ud2指令...


8
UB 意味着一切都不确定,理智早已被抛弃,没有任何抱怨的基础留下了! - Deduplicator
1
可能是[未定义、未指定和实现定义行为]的重复问题。 (https://dev59.com/63E95IYBdhLWcg3wPbZ7) - Deduplicator
3
在编译器“知道”存在未定义行为的情况下,生成一个只会使应用程序崩溃的指令是绝对可以接受的,甚至可以说是最安全的做法。 - gnasher729
4
这并不是生成无效指令,而是生成一个有效指令,其效果是触发一个无效操作码异常。这会给你造成任何问题吗? - Keith Thompson
2
正如我在答案中所述,这是有效的指令,编译器在某些方面是有帮助的,因为长时间忽略一个严重的问题可能会更糟糕。 - Shafik Yaghmour
显示剩余2条评论
1个回答

17

ud2指令是一个"有效指令",代表未定义指令,并生成无效操作码异常。当程序调用未定义行为时,clang和显然gcc可以生成此代码。

从上面的clang链接中,解释了其原因如下:

将存储为 null 和通过 null 指针调用的内容转换为 __builtin_trap() 调用(在 x86 上转换为像 "ud2" 的陷阱指令)。这些在优化代码中经常发生(作为内联和常量传播等其他转换的结果),我们曾经只是删除包含它们的块,因为它们“显然是无法到达的”。虽然从严谨的语言律师的角度来看,这是严格正确的,但我们很快发现人们偶尔会引用空指针,并且让代码执行跳转到下一个函数的顶部使问题非常难以理解。从性能的角度来看,暴露这些的最重要方面是压缩下游代码。因此,Clang 将这些转换为运行时陷阱:如果其中一个实际上被动态地到达,则程序立即停止并可以进行调试。这样做的缺点是,我们通过拥有这些操作并具有控制其谓词的条件来略微膨胀代码。
一旦您调用未定义行为,程序的行为就是不可预测的。这里的哲学是:最好彻底崩溃并向开发人员指示出严重错误,并允许他们从正确的位置进行调试,而不是产生似乎正常工作但实际上已经损坏的程序。
正如Ruslan所指出的那样,“有效”是指它保证引发无效操作码异常,而不是其他未使用的序列,这些序列在将来可能变为有效。

4
“ud2”和“dw 0xffff”一样有效。使它“有效”的唯一原因是,它保证始终无效,而其他无效的字节序列可以被视为保留,并且可能在未来的CPU实现中变得有效。 - Ruslan

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