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

我知道当你在C程序中做某些事情时,结果是不确定的。 但是,编译器不应该生成无效 (机器)代码,对吧? 如果代码做错了,或者代码生成了段错误或其他什么,那将是合理的……

这应该根据编译器规范发生,还是编译器中的错误?

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

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

我正在使用-O3编译。 这不应该生成无效的硬件指令,对吧? 使用-O0 ,运行代码时会出现段错误。 这看起来更加明智。

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

ud2指令是一个“有效指令” ,它代表未定义指令并生成无效的操作码exception铿锵声 ,显然当程序调用未定义的行为时, gcc可以生成此代码。

从上面的clang ,基本原理解释如下:

存储为null并通过空指针调用将转换为__builtin_trap()调用(在x86上变为陷阱指令,如“ud2”)。 这些在优化代码中一直发生(作为其他转换的结果,如内联和常量传播),我们过去只是删除包含它们的块,因为它们“显然无法访问”。

虽然(从迂腐的语言律师角度来看)这是完全正确的, 但我们很快就知道人们偶尔会取消引用空指针,并且让代码执行只是落入下一个函数的顶部,这使得理解问题变得非常困难 。 从性能角度来看,暴露这些的最重要方面是压缩下游代码。 因此,clang将这些转换为运行时陷阱: 如果其中一个实际上是动态到达的,程序会立即停止并可以调试 。 这样做的缺点是我们通过执行这些操作并具有控制其谓词的条件来略微膨胀代码。

在一天结束时,一旦调用未定义的行为,程序的行为就无法预测。 这里的理念是,最好是崩溃并让开发人员指出某些事情是严重错误的,并允许他们调试正确的点而不是生成一个似乎有效但实际上已经破解的程序。

正如Ruslan所指出的,它在某种意义上是“有效的”,它保证引发无效的操作码exception,而不是将来可能有效的其他未使用的序列。