C按位计算,并给出与-O0和-O2不同的结果
我正在使用Bochs和DOSBox作为参考的PC仿真器上工作 当模拟“NEG-Ed”指令(双字的两个补码否定)时,如果使用C按位计算,并给出与-O0和-O2不同的结果,c,gcc,C,Gcc,我正在使用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) { u
-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=%X\n", value, negation, sign);
return 0;
}
#包括
#包括
#包括
#包括
int main(int argc,常量字符**argv)
{
uint32_t value=strtol(argv[1],NULL,16);
uint32_t否定=-(int32_t)(值);
布尔符号=否定&0x8000000;
printf(“值=%X,否定=%X,符号=%X\n”,值,否定,符号);
返回0;
}
-(int32_t)(值)代码>零件取自Bochs的NEG_EdM()
函数;对于等效操作,DOSBox不强制转换为带符号的int
如果您使用GCC 10使用-O2
选项编译此程序,并使用十六进制值0x8000000
作为输入,您将得到错误的符号结果:
值=80000000,否定=80000000,符号=0
使用-O0
编译时,结果是正确的:
值=80000000,否定=80000000,符号=1
这是未定义的行为吗
据我所知,向有符号整数和无符号整数转换/从有符号整数和无符号整数转换的定义很好,按位&对无符号值转换也是如此。您的代码中存在一些问题:
strtol(“0x8000000”,NULL,16)
返回的值取决于类型long
的范围:如果类型long
有32位,则返回值应为long\u MAX
,即2147483647
,而如果long
更大,则返回2147483648
。将这些值转换为uint32\u t
不会更改值,因为两者都在uint32\u t
范围内。在系统上键入long
似乎有64位。您可以使用strtoul()
而不是strtol()
来避免这种实现定义的行为
- 无需将中介强制转换为
(int32\u t)
:对无符号值求反定义良好,并且-0x8000000
具有类型uint32\u t
的值0x8000000
- 此外,这种转换会适得其反,并且可能导致观察到的行为,如否定值
INT32_MIN
由于有符号算术溢出而具有未定义的行为。启用优化后,编译器确定您正在提取符号,就像通过bool sign=-(int32_t)value<0
提取符号一样,并将此表达式简化为bool sign=(int32_t)value>0
,这对于编译器认为任何行为正常的int32_MIN
之外的所有值都是正确的,因为行为是未定义的。您可以在上查看代码
- 使用type
bool
时不包括
:程序不应编译。这是复制/粘贴错误,还是编译为C++?C99\u Bool
语义在initialization语句中添加了隐式测试,但最好将其显式化并编写:
bool sign = (negation & 0x80000000) != 0;
- 最后,将
uint32\u t
值传递给%X
转换说明符的printf
。如果类型int
在您的平台上少于32位,则这是不正确的。使用
中的宏
请尝试此修改版本:
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <inttypes.h>
int main(int argc, const char **argv)
{
uint32_t value = strtoul(argv[1], NULL, 16);
uint32_t negation = -value;
bool sign = (negation & 0x80000000) != 0;
printf("value=%"PRIX32", negation=%"PRIX32", sign=%d\n", value, negation, sign);
return 0;
}
#包括
#包括
#包括
#包括
#包括
int main(int argc,常量字符**argv)
{
uint32_t value=strtoul(argv[1],NULL,16);
uint32_t否定=-值;
布尔符号=(否定&0x8000000)!=0;
printf(“值=%“PRIX32”,否定=%“PRIX32”,符号=%d\n”,值,否定,符号);
返回0;
}
您的不幸经历源于有符号算术溢出的未定义行为。编译器可以利用未定义的行为来实现高级优化,例如在(int i=0;i>0;i++)
中删除for(int i=0;i>0;i++)中的结束测试,以及转换void f(int i)等更明显但非琐碎的优化{int j=i*2/2;..
到int j=i;
对于超过0x3fffff
的值,可能表现出不同的行为
其他语言(例如:java)尝试删除未定义的行为,并完全指定两种语言的补充实现和行为,因此不会执行这些优化
标准C语言委员会似乎倾向于进行更多的优化,但在边境案件中可能会出现一些意外情况,这可能很难发现和解决。您的示例完美地说明了这个问题。未定义行为的来源
问题的关键部分在于对-(int32_t)值的否定
此时,值
为80000016(231)。由于该值在int32_t
中不可表示,因此转换受C 2018 6.3.1.3 3的控制,表示行为由实现定义。将80000016包装到int32_t
模232产生−80000016
然后应用否定运算符-
−80000016当然是80000016,但这在int32\t
中不可表示。2该行为受C 2018 6.5的约束:
如果在表达式求值期间出现异常情况(即,如果结果未在数学上定义或不在其类型的可表示值范围内),则行为未定义
因此,求反具有未定义的行为。当使用-O0
时,编译器生成简单的直接代码。这将进行包装,为输入位80000016(表示−80000016作为有符号32位整数)。使用-O2
时,编译器将对
bool sign = - (uint32_t) x & 0x80000000u;
bool sign = - (uint32_t) x >> 31;