C按位AND使用-O0和-O2给出不同的结果

我正在开发一个同时使用 Bochs 和 DOSBox 作为参考的 PC 模拟器。

在模拟“NEG Ed”指令(双字的补码否定)时,如果我使用-O0而不是-O2.

这是一个只有相关位的测试程序:

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <stdbool.h>

int main(int argc, const char **argv)
{
    uint32_t value = strtol(argv[1], NULL, 16);
    uint32_t negation = -(int32_t)(value);
    bool sign = negation & 0x80000000;

    printf("value=%X, negation=%X, sign=%Xn", value, negation, sign);
    
    return 0;
}

-(int32_t)(value);一部分从Bochs的截取NEG_EdM()功能; 对于等效操作 DOSBox 不会强制转换为有符号整数。

如果您使用该-O2选项使用GCC 10 编译此程序并使用十六进制值0x80000000作为输入,您将得到以下错误结果sign

value=80000000, negation=80000000, sign=0

编译时-O0结果正确:

value=80000000, negation=80000000, sign=1

这是未定义的行为吗?

据我所知,有符号和无符号整数的转换是明确定义的,按位 & 在无符号值上也是如此。

回答

未定义行为的来源

问题的关键部分在于 的否定-(int32_t)value1

此时,value是 80000000 16 (2 31 )。由于在 中无法表示int32_t,因此转换受 C 2018 6.3.1.3 3 的约束,其中表示行为是实现定义的。GCC 10.2 将其定义为包装模 2 N,其中目标宽度为N位。将 80000000 16包装到int32_t模 2 32产生 ?80000000 16

然后-应用否定运算符。?80000000 16的数学否定当然是 80000000 16,但这在 中无法表示int32_t2行为受 C 2018 6.5 5 约束:

如果在对表达式求值期间出现异常情况(即,如果结果未在数学上定义或不在其类型的可表示值范围内),则行为未定义。

因此,否定具有未定义的行为。当-O0被使用时,编译器会生成简单的直接代码。Godbolt显示它产生一个否定指示该包裹物,产生输出80000000 16于输入比特80000000 16(其将代表?80000000 16作为一个符号的32位整数)。当-O2被使用时,编译器执行复杂的分析和方案的变换,以及缺乏定义的行为叶的编译器可以自由地产生任何结果。事实上,Godbolt 显示否定指令不存在。实际上,编译器“知道”否定一个int32_t值永远不会产生具有 2 31 在具有定义行为的程序中设置的位。

优化讨论

考虑可表示的值的范围int32_t是?2 31到2 31 ?1。这些的数学否定是 ?(2 31 ?1) 到 2 31。但是,2 31溢出,导致异常情况。不溢出的结果范围是?(2 31 ?1) 到2 31 ?1。因此,在具有定义行为的程序中,只会出现这些结果,因此编译器可能会像只出现这些结果一样进行优化。在这些结果中没有一个是 2 31位集。因此,在具有定义行为的程序中,negation & 0x80000000始终为零,编译器可能会基于此生成代码。

使固定

看来您想要测试是否将符号位设置int32_t为使用二进制补码取反的 ,即包装结果模 2 32。为此,可以使用无符号算术。如果x是一个int32_t值或uint32_t包含表示该值的位,则取反值的符号位可以通过以下任一方式获得:

bool sign = - (uint32_t) x & 0x80000000u;
bool sign = - (uint32_t) x >> 31;

脚注

1我们推断出long的宽度大于 32 位。不是吗,strtol("0x80000000", NULL, 16)会返回LONG_MAX,根据 C 2018 7.22.1.4 8。这将在uint32_tand 中表示int32_t,因此value将被初始化为LONG_MAX,转换为int32_t将保留该值,negation将是?LONG_MAX,并且sign在程序的优化和未优化版本中都为零。

2如果int32_t小于int,操作数将被提升到int否定之前,并且数学结果将是可表示的。您使用的 GCC 版本和选项并非如此,我们可以从观察到的结果中推断出这一点。


以上是C按位AND使用-O0和-O2给出不同的结果的全部内容。
THE END
分享
二维码
< <上一篇
下一篇>>