C 为什么整数除以-1(负1)会导致FPE?
我的任务是解释一些看似奇怪的C代码行为(在x86上运行)。我可以很容易地完成其他的事情,但这件事让我很困惑 代码段1输出C 为什么整数除以-1(负1)会导致FPE?,c,gcc,x86,arm64,floating-point-exceptions,C,Gcc,X86,Arm64,Floating Point Exceptions,我的任务是解释一些看似奇怪的C代码行为(在x86上运行)。我可以很容易地完成其他的事情,但这件事让我很困惑 代码段1输出-2147483648 int a = 0x80000000; int b = a / -1; printf("%d\n", b); 代码片段2不输出任何内容,并给出一个浮点异常 int a = 0x80000000; int b = -1; int c = a / b; printf("%d\n", c); 我很清楚代码片段1产生
-2147483648
int a = 0x80000000;
int b = a / -1;
printf("%d\n", b);
代码片段2不输出任何内容,并给出一个浮点异常
int a = 0x80000000;
int b = -1;
int c = a / b;
printf("%d\n", c);
我很清楚代码片段1产生结果的原因(1+~INT\u MIN==INT\u MIN
),但我不太明白整数除以-1如何生成FPE,也不能在我的Android手机上重现它(AArch64,GCC 7.2.0)。代码2的输出与代码1相同,没有任何异常。这是x86处理器的一个隐藏bug特性吗
这项作业没有说明任何其他内容(包括CPU体系结构),但由于整个课程都基于桌面Linux发行版,您可以放心地假设它是一款现代的x86
编辑:我联系了我的朋友,他在Ubuntu 16.04(英特尔卡比湖,GCC 6.3.0)上测试了代码。结果与指定的内容一致(代码1输出所述内容,代码2使用FPE崩溃)。非常可能发生的事情,有时确实会发生 你的问题在C语言中没有意义。但是您可以获得汇编代码(例如,由
gcc-O-fverbose asm-S
生成)并关心机器代码的行为
在x86-64上,Linux整数溢出(以及整数除零,IIRC)会给出一个SIGFPE
信号。看
顺便说一句,在PowerPC上,据说整数除以零在机器级别得到-1(但是一些C编译器生成额外的代码来测试这种情况)
问题中的代码在C中是未定义的行为。生成的汇编程序代码有一些已定义的行为(取决于和处理器)
(完成作业是为了让你阅读更多关于UB的内容,尤其是你绝对应该阅读的内容)很多事情都可能发生,有时确实会发生
你的问题在C语言中没有意义。但是您可以获得汇编代码(例如,由gcc-O-fverbose asm-S
生成)并关心机器代码的行为
在x86-64上,Linux整数溢出(以及整数除零,IIRC)会给出一个SIGFPE
信号。看
顺便说一句,在PowerPC上,据说整数除以零在机器级别得到-1(但是一些C编译器生成额外的代码来测试这种情况)
问题中的代码在C中是未定义的行为。生成的汇编程序代码有一些已定义的行为(取决于和处理器)
(完成作业是为了让您阅读更多关于UB的内容,尤其是您应该绝对阅读的内容)有符号的
int
如果出现以下情况,则两个补码中的除法是未定义的:
INT\u MIN
(=0x8000000
如果INT
为int32\u t
),除数为-1
(以二补形式,
-INT\u MIN>INT\u MAX
,这会导致整数溢出,这是C中未定义的行为)由于您正在通过违反规则2来调用未定义的行为,因此任何事情都可能发生,并且在发生时,您平台上的这一特定事情恰好是由处理器生成的FPE信号。Signed
int
如果出现以下情况,则二的除法是未定义的:
INT\u MIN
(=0x8000000
如果INT
为int32\u t
),除数为-1
(以二补形式,
-INT\u MIN>INT\u MAX
,这会导致整数溢出,这是C中未定义的行为)INT_MIN/-1
是导致#DE(除法错误)的情况之一这确实是商超出范围的一种特殊情况,一般来说这是可能的,因为idiv
将额外的宽除数除以除数,如此多的组合会导致溢出-但是INT_MIN/-1
是唯一一种不是div-by-0的情况,通常可以从更高级别的语言访问,因为它们调用y不公开超宽红利功能
Linux令人烦恼地将#DE映射到SIGFPE,这可能让所有第一次处理它的人都感到困惑。在x86上,如果实际使用该操作进行除法(这对于常量参数来说并不是必需的,即使对于已知为常量的变量也不是,但无论如何都会发生),INT_MIN/-1
是导致#DE(除法错误)的情况之一这确实是商超出范围的一种特殊情况,一般来说这是可能的,因为idiv
将额外的宽除数除以除数,如此多的组合会导致溢出-但是INT_MIN/-1
是唯一一种不是div-by-0的情况,通常可以从更高级别的语言访问,因为它们调用y不公开超宽红利功能
Linux令人恼火地将#DE映射到SIGFPE,这可能让所有第一次处理它的人都感到困惑。这里有四件事:
行为解释了您的两个版本之间的差异:gcc-O0
与idiv
(而neg
恰好使用clang-O0
编译它们)。以及为什么即使使用compile ti也会出现这种情况idiv
int a = 0x80000000; int b = -1; // debugger can stop here on a breakpoint and modify b. int c = a / b; // a and b have to be treated as runtime variables, not constants. printf("%d\n", c);
# int c = a / b from x86_fault() mov eax, DWORD PTR [rbp-4] cdq # dividend sign-extended into edx:eax idiv DWORD PTR [rbp-8] # divisor from memory mov DWORD PTR [rbp-12], eax # store quotient
# int c = a / b from x86_fault() (which doesn't fault on AArch64) ldr w1, [sp, 12] ldr w0, [sp, 8] # 32-bit loads into 32-bit registers sdiv w0, w1, w0 # 32 / 32 => 32 bit signed division str w0, [sp, 4]
int x86_fault() { int a = 0x80000000; int b = -1; int c = a / b; return c; }
x86_fault: mov eax, -2147483648 ret
x86_fault: ret
int *local_address(int a) { return &a; } local_address: xor eax, eax # return 0 ret void foo() { int *p = local_address(4); *p = 2; } foo: mov DWORD PTR ds:0, 0 # store immediate 0 into absolute address 0 ud2 # illegal instruction
1000_0000_0000_0000_0000_0000_0000
0111_1111_1111_1111_1111_1111_1111 + 1 => 1000_0000_0000_0000_0000_0000_0000 !!! the same original number.
1000_0000_0000_0000_0000_0000_0000 & 0111_1111_1111_1111_1111_1111_1111 => 0000_0000_0000_0000_0000_0000_0000