在整数除以零的平台上触发浮点exception?
在另一个问题中,有人想知道为什么当他们在C ++程序中有一个整除零时,他们得到了一个“浮点错误”。 围绕这个问题进行了讨论,其中一些人声称浮点exception事实上从来没有被浮动除以零产生,而是只在整数除以零时产生。
这听起来很奇怪,因为我知道:
-
所有Windows平台上的x86和x64上的MSVC编译代码报告int除以零为“0xc0000094:整数除零”,浮点除以零作为0xC000008E“浮点除零”(启用时)
-
IA-32和AMD64 ISA将
#DE
(整数#DE
除外)指定为中断0.浮点exception触发中断16(x87浮点)或中断19(SIMD浮点)。 -
其他硬件也有类似的不同中断( 例如, PPC在float-div-by-zero上产生0x7000,并且根本不捕获int / 0)。
-
我们的应用程序用
_controlfp_s
内部函数(最终是stmxcsr
op)取消了被零除的浮点exception,然后捕获它们用于debugging目的。 所以我在实践中已经明确地看到了IEEE754除零的例外。
所以我得出结论,有一些平台将intexception报告为浮点exception,例如x64 Linux(不pipeALUpipe道是否提升所有算术错误的SIGFPE) 。
什么其他操作系统(或C / C ++运行时,如果您是操作系统)报告整数div-by-zero作为浮点exception?
我不确定目前的情况如何,但目前的情况是,FPexception检测支持与整数非常不同。 整数除法很常见。 如果引发exception, POSIX要求它提出SIGFPE
。
但是,你可以理清它是什么types的SIGFPE,看看它实际上是一个除法exception。 (但不一定为零,但是:2的补码INT_MIN
/ -1
除法陷阱,当64b / 32b除法的商不适合32b输出寄存器时, x86的div
和idiv
也会陷入陷阱。
glibc手册解释了BSD和GNU系统向SIGFPE
的信号处理程序提供了一个额外的参数,这个参数将被FPE_INTDIV_TRAP
除以零。 在siginfo_t
包含该成员的系统上,POSIX将文件FPE_INTDIV_TRAP
作为siginfo_t
的int si_code
字段的可能值。
IDK,如果Windows首先提供了一个不同的exception,或者它将事物捆绑到像Unix一样的算术exception中。 如果是这样,默认处理程序解码额外的信息,告诉你它是什么样的exception。
POSIX和Windows都使用“除零”来覆盖所有的整数除法,所以显然这是通用的简写。 对于那些了解INT_MIN / -1(有2的补码)的人来说是一个问题,“除零”这个短语可以被看作是除法exception的同义词。 这句话立即指出了人们不知道为什么整数除法可能成为问题的常见情况。
FPexception语义
在大多数操作系统/ C ABI中,FPexception在用户空间进程默认情况下被屏蔽。
这是有道理的,因为IEEE浮点可以表示无穷,并且有NaN传播错误到所有将来的计算使用的值。
-
NaN
- 如果
x
是有限的:x/0.0
=>+/-Inf
,其符号为x
这甚至允许这样的事情在exception被掩盖时产生明智的结果:
double x = 0.0; double y = 1.0/x; // y = +Inf double z = 1.0/y; // z = 1/Inf = 0.0, no FP exception
FP与整数错误检测
检测错误的FP方式非常好:当exception被屏蔽时,它们在FP状态寄存器中设置一个标志而不是陷印。 (例如,用于SSE指令的x86 MXCSR)。 该标志一直保持设置,直到手动清除,所以你可以检查一次(例如循环之后),看看发生了什么exception,而不是发生了什么。
有一些build议提出,如果在计算序列的任何一点发生溢出,都要logging类似的“粘性”整数溢出标志 。 在某些情况下,允许整数除法例外会很好,但在其他情况下是危险的(例如,在地址计算中,您应该陷入,而不是潜在地存储到一个虚构的位置)。
但是,在x86上,如果在一系列计算过程中检测是否发生整数溢出,则需要在每个计算结果之后放置一个条件分支,因为标志只是被覆盖。 MIPS有一个add
指令,可以捕获已签名溢出,以及一个永不陷阱的未签名指令。 所以整数exception检测和处理是很less标准化。
整数除法没有生成NaN或Inf结果的选项, 所以它以这种方式工作是有意义的 。
由整数除法产生的整数位模式将是错误的,因为它将表示一个特定的有限值。
但是,在x86上,如果“浮点无效”exception被屏蔽,则使用cvtsd2si
或任何类似的转换指令将超出范围的浮点值转换为整数将产生“整数不确定”值。 除了符号位之外,该值是全零。 即INT_MIN
。
(请参阅英特尔手册, x86标签wiki中的链接。
什么其他操作系统(或C / C ++运行时,如果您是操作系统)报告整数div-by-zero作为浮点exception?
答案取决于你是在内核空间还是在用户空间 。 如果你在内核空间,你可以把“i / 0”放在kernel_main()
,让你的中断处理程序调用一个exception处理程序并停止你的内核。 如果你在用户空间,答案取决于你的操作系统和编译器设置。
AMD64硬件将中断0(不同于中断16(x87浮点除外)和中断19(SIMD浮点除外))指定整数除零。
Divide-by-zero除以div
除以零指令。 讨论x87 FPU不在这个问题的范围之内。
其他硬件也有类似的不同中断(例如,PPC在float-div-by-zero上产生0x7000,并且根本不捕获int / 0)。
更具体地说, 00700
映射到exceptiontypes“程序”,其中包括一个浮点启用的exception。 如果使用浮点指令尝试除以零,则会引发这种exception。
另一方面,整数除法是每个PPC PEM的未定义行为:
8-53 divw
如果试图执行任一个分组(0x8000_0000÷-1或÷0),那么rD的内容是不确定的,CR0字段的LT,GT和EQ位的内容也是未定义的(如果Rc = 1 )。 在这种情况下,如果OE = 1,则OV被置位。
我们的应用程序用
_controlfp_s
内部函数(最终是stmxcsr op)取消了被零除的浮点exception,然后捕获它们用于debugging目的。 所以我在实践中已经明确地看到了IEEE754除零的例外。
我认为你的时间在编译时而不是在运行时间上花费了零。
对于用户空间,这发生在运行在POWER上的AIX,运行在PA-RISC上的HP-UX,运行在x86-64上的Linux,运行在x86-64上的macOS,运行在Alpha上的Tru64以及运行在SPARC上的Solaris上。
在编译时避免被零除以更好。