我发现了几个讨论有符号和无符号编译器优化的问题。 一般的结论是,由于溢出的未定义行为,signed 允许进行更多优化。 但是,我没有找到任何关于签名如何与操作重新排序一起玩的讨论。
考虑表达式
a - b + c
如果所有值都是无符号的,编译器总是可以重新排序操作(首先添加
a
和 c
可能会提高性能)。
如果所有值都是有符号的,编译器必须证明重新排序不会导致溢出发生。一般来说,
a + c
可能会溢出,因此编译器在重新排序方面受到限制。
我是否正确认为编译器可以更自由地为无符号值重新排序操作?
如果所有值都是有符号的,编译器必须证明重新排序不会导致溢出发生。
这个前提是错误的。如果 C 程序中没有未定义的行为,编译器的负担就是产生该行为。 C 标准不限制它是如何做到的。如果它重新排序
a - b + c
到 a + c - b
但是它使用行为适当的指令这样做,那么 a + c - b
将计算与 a - b + c
相同的结果。
考虑使用 32 位
int
对象的示例:a
是 7FFFFFF016,b
是 10016,c
是 2016。那么a - b
就是7FFFFEF016,a - b + c
就是7FFFFF1016。如果编译器使用产生 8000001016的加法指令计算
a + c
,然后使用产生 7FFFFF1016的减法指令计算
a + c - b
,那么结果是正确的。
这是有效的,因为加法和减法指令不“关心”溢出并产生所需的结果,即使它们超出了
int
范围。不仅这样的指令在当今的处理器上很常见,而且相同的加法和减法指令用于有符号和无符号算术,因为加法和减法的位模式对于无符号和二进制补码表示是相同的。 (比较指令不同,因为当解释为无符号或二进制补码时,这些位具有不同的含义。)
如果源代码字面上被更改为
a + c - b
,程序将具有未定义的行为这一事实是无关紧要的。编译器通常不会通过名义上将 C 源代码重新排列为随后编译的其他 C 源代码来运行。编译器通常通过将 C 语义转换为某种内部表示,然后生成汇编代码来实现这些语义来进行操作。