- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
我的任务是解释一些看似奇怪的C代码行为(在x86上运行)。我可以轻松完成所有其他工作,但是这确实让我感到困惑。
代码段1输出-2147483648
int a = 0x80000000;
int b = a / -1;
printf("%d\n", b);
Floating point exception
int a = 0x80000000;
int b = -1;
int c = a / b;
printf("%d\n", c);
1 + ~INT_MIN == INT_MIN
)生成结果的原因,但是我不太了解如何将-1除以整数会生成FPE,也无法在Android手机(AArch64,GCC 7.2)上重现它。 0)。代码2的输出与代码1相同,没有任何例外。它是x86处理器的隐藏错误功能吗?
最佳答案
这里有四件事:gcc -O0
行为说明了两个版本之间的区别。 (而clang -O0
恰巧同时使用idiv
编译它们)。以及为什么即使使用编译时常数操作数也能得到它。
x86 idiv
故障行为与ARM上除法指令的行为
如果整数运算导致传递信号,则POSIX要求它为SIGFPE:On which platforms does integer divide by zero trigger a floating point exception?但是POSIX不需要为任何特定的整数运算而设陷阱。 (这就是为什么x86和ARM可以不同的原因)。
单一Unix规范defines SIGFPE作为“错误的算术运算”。它以浮点数来混淆地命名,但是在FPU处于默认状态的正常系统中,只有整数运算会提高它。在x86上,只有整数除法。在MIPS上,编译器可以使用add
instead of addu
进行带符号的数学运算,因此您可能会在带符号的添加溢出中获取陷阱。 (gcc uses addu
even for signed,但是未定义行为的检测器可能使用add
。)
C未定义的行为规则(有符号的溢出和除法)使gcc发出可以在这种情况下捕获的代码。
没有选项的gcc与gcc -O0
相同。
-O0
减少编译时间并使调试产生预期的结果。这是默认值。
这说明了两个版本之间的区别:gcc -O0
不仅不尝试优化,还主动进行反优化,以使asm独立实现函数中的每个C语句。这使gdb
's jump
command安全地工作,使您可以跳到函数内的另一行,并像在C语言源中真正跳来跳去一样。
它也不能假设有关语句之间的变量值,因为您可以使用set b = 4
更改变量。这显然对性能造成灾难性的不利影响,这就是为什么-O0
代码运行速度比普通代码慢几倍的原因,也是为什么optimizing for -O0
specifically is total nonsense的原因。由于所有存储/重新加载,甚至缺少最明显的优化,它也使-O0
asm输出really noisy and hard for a human to read。
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);
a/b
,
gcc -O0
必须发出代码以从内存中重新加载
a
和
b
,并且不对其值进行任何假设。
int c = a / -1;
时,您无法使用调试器更改
-1
,因此gcc可以并且确实以与xcc
int c = -a;
或AArch64
neg eax
指令实现
neg w0, w0
相同的方式来实现该语句,被负载(a)/商店(c)包围。在ARM32上,它是一个
rsb r3, r3, #0
(反减:
r3 = 0 - r3
)。
-O0
不会执行该优化。它仍然对
idiv
使用
a / -1
,因此两个版本在使用clang的x86上都会出错。为什么gcc完全“优化”了?请参见
Disable all optimization options in GCC。 gcc总是通过内部表示形式进行转换,而-O0只是生成二进制文件所需的最少工作量。它没有一种“笨拙而文字化”的模式,该模式试图使asm尽可能地类似于源。
idiv
与AArch64
sdiv
:
# 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
imul r32,r32
不同,没有2操作数
idiv
没有上半部的分红输入。无论如何,这并不重要; gcc仅将其与
edx
=
eax
中符号位的副本一起使用,因此它实际上是在执行32b / 32b => 32b商+余数。
As documented in Intel's manual,
idiv
在以下位置引发#DE:
int result = long long / int
。但是gcc不能进行这种优化,因为它不允许创建有故障的代码,而不是遵循C整数提升规则并进行64位除法然后截断为
int
。它也
doesn't optimize even in cases where the divisor is known to be large enough that it couldn't #DE
cdq
)时,唯一可以溢出的输入是
INT_MIN / -1
。 “正确”商是一个33位带符号整数,即带前导零符号位的正
0x80000000
,使其成为正2的补码带符号整数。由于这不适用于
eax
,因此
idiv
会引发
#DE
异常。然后内核提供
SIGFPE
。
# 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]
sdiv
documentation没有提及任何例外。
INT_MIN
/
-1
在C中是未定义的行为,就像所有有符号整数溢出一样。这使编译器可以在x86之类的机器上使用硬件划分指令,而无需检查特殊情况。如果不必出错,未知的输入将需要运行时比较和分支检查,并且没人希望C要求这样做。
a
和
b
在
a/b
运行时仍具有其设置值。然后,它可以看到程序具有未定义的行为,因此可以执行所需的任何操作。 gcc选择像从
INT_MIN
产生
-INT_MIN
一样产生。
abs(x)
仍然可以是负数。
int x86_fault() {
int a = 0x80000000;
int b = -1;
int c = a / b;
return c;
}
gcc6.3 -O3
对此进行编译
x86_fault:
mov eax, -2147483648
ret
clang5.0 -O3
编译为(即使使用-Wall -Wextra`也没有警告):
x86_fault:
ret
eax
中的所有垃圾,或加载NULL指针和非法指令。例如对于x86-64,使用gcc6.3 -O3:
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
-O0
的情况没有让编译器在编译时看到UB,因此您获得了“预期”的asm输出。
关于c - 为什么整数除以-1(负数)会导致FPE?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58373496/
我编写了一个处理浮点异常信号的程序,我使用的是 Ubuntu 10.4。 这是我的源代码: #include #include #include #include sigjmp_buf mar
关闭。这个问题不满足Stack Overflow guidelines .它目前不接受答案。 想改善这个问题吗?更新问题,使其成为 on-topic对于堆栈溢出。 3年前关闭。 Improve thi
背景 我有一个程序有时会因(主要)被零除而抛出浮点异常,尽管已对除以零值进行了检查。这可能与 floating point speculation 有关但我不确定。 我已经使用 feenableexc
在 C 中,您可以使用 SIGFPE 定义浮点异常处理程序。 例如,您将如何使用 Rust 定义处理溢出的函数? 参见 this function有关设置回调以在 C 中运行的示例。 最佳答案 Rus
我的任务是解释 C 代码(在 x86 上运行)的一些看似奇怪的行为。我可以轻松完成其他所有事情,但这个让我很困惑。 Code snippet 1 outputs -2147483648 int a =
在数值应用程序中,我想知道在计算完成后是否发生浮点异常。默认情况下,浮点除法和无效操作会被静默忽略。 我的尝试是启用我关心的 FPE,通过设置标志并再次禁用它们来处理 SIGFPE 以允许继续执行:
我正在调试我添加的更大的数值程序。它是用 fortran90 编写的,使用 gfortran(适用于 Mac 的最新版本)编译,我正在使用 gdb(同样适用于 Mac 的最新版本)对其进行调试。 我的
我有一个用 Fortran 编写的库,它使用 Intel 的 MKL(静态链接)。我正在使用 ifort 进行编译和链接。 当我使用 -fpe0 选项将我的库(动态)与一些其他代码链接时,我在 MKL
我正在构建一个项目,以使用 FFmpeg 1.1 在 Android 中查看来自 IP 摄像机的视频源。 我试图在 Android 项目中使用 swresample,但在调用 swr_convert
我尝试在我的 Centos 5.7 机器上运行 Xvfb 来获取网站的缩略图。 我关注这个Xvfb + Firefox site并在我的 Gnome Centos 5.7 上安装 Xvfb、firef
我是一名优秀的程序员,十分优秀!