简体   繁体   English

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

[英]C code with undefined results, compiler generates invalid code (with -O3)

I know that when you do certain things in a C program, the results are undefined. 我知道当你在C程序中做某些事情时,结果是不确定的。 However, the compiler should not be generating invalid (machine) code, right? 但是,编译器不应该生成无效 (机器)代码,对吧? It would be reasonable if the code did the wrong thing, or if the code generated a segfault or something... 如果代码做错了,或者代码生成了段错误或其他什么,那将是合理的......

Is this supposed to happen according to the compiler spec, or is it a bug in the compiler? 这应该是根据编译器规范发生的,还是编译器中的错误?

Here's the (simple) program I'm using: 这是我正在使用的(简单)程序:

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

I'm compiling with -O3 . 我正在使用-O3编译。 That shouldn't generate invalid hardware instructions though, right? 那不应该生成无效的硬件指令,对吧? With -O0 , I get a segfault when I run the code. 使用-O0 ,运行代码时会出现段错误。 That seems a lot more sane. 这看起来更加明智。

Edit: It's generating a ud2 instruction... 编辑:它正在生成一个ud2指令......

The ud2 instruction is a "valid instruction" and it stands for Undefined Instruction and generates an invalid opcode exception clang and apparently gcc can generate this code when a program invokes undefined behavior. ud2指令是一个“有效指令” ,它代表未定义指令并生成无效的操作码异常铿锵声 ,显然当程序调用未定义的行为时, gcc可以生成此代码。

From the clang link above the rationale is explained as follows: 从上面的clang链接的基本原理解释如下:

Stores to null and calls through null pointers are turned into a __builtin_trap() call (which turns into a trapping instruction like "ud2" on x86). 存储为null并通过空指针调用将转换为__builtin_trap()调用(在x86上变为陷阱指令,如“ud2”)。 These happen all of the time in optimized code (as the result of other transformations like inlining and constant propagation) and we used to just delete the blocks that contained them because they were "obviously unreachable". 这些在优化代码中一直发生(作为其他转换的结果,如内联和常量传播),我们过去只是删除包含它们的块,因为它们“显然无法访问”。

While (from a pedantic language lawyer standpoint) this is strictly true, we quickly learned that people do occasionally dereference null pointers, and having the code execution just fall into the top of the next function makes it very difficult to understand the problem . 虽然(从迂腐的语言律师角度来看)这是完全正确的, 但我们很快就知道人们偶尔会取消引用空指针,并且让代码执行只是落入下一个函数的顶部,这使得理解问题变得非常困难 From the performance angle, the most important aspect of exposing these is to squash downstream code. 从性能角度来看,暴露这些的最重要方面是压缩下游代码。 Because of this, clang turns these into a runtime trap: if one of these is actually dynamically reached, the program stops immediately and can be debugged . 因此,clang将这些转换为运行时陷阱: 如果其中一个实际上是动态到达的,程序会立即停止并可以调试 The drawback of doing this is that we slightly bloat code by having these operations and having the conditions that control their predicates. 这样做的缺点是我们通过执行这些操作并具有控制其谓词的条件来略微膨胀代码。

at the end of the day once your are invoking undefined behavior the behavior of your program is unpredictable. 在一天结束时,一旦调用未定义的行为,程序的行为就无法预测。 The philosophy here is that is probably better to crash hard and give the developer an indication that something is seriously wrong and allow them to debug fro the right point than to produce a program that seems to work but actually is broken. 这里的理念是,最好是崩溃并让开发人员指出某些事情是严重错误的,并允许他们从正确的点调试,而不是生成一个似乎有效但实际上已被破坏的程序。

As Ruslan notes, it is "valid" in the sense that it guaranteed to raise an invalid opcode exception as opposed to other unused sequences which may in the future become valid. 正如Ruslan所指出的那样,它在某种意义上是“有效的”,它保证引发无效的操作码异常,而不是将来可能有效的其他未使用的序列。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM