Causing a divide overflow error (x86)
我有一些关于x86或x86_64体系结构上的划分溢出错误的问题。最近,我一直在阅读有关整数溢出的知识。通常,当算术运算导致整数溢出时,将置位FLAGS寄存器中的进位或溢出位。但是很显然,根据本文所述,除法运算导致的溢出不会设置溢出位,而是会触发硬件异常,类似于将其除以零时的情况。
现在,除法导致的整数溢出比乘法要少得多。只有几种方法可以触发除法溢出。一种方法是做类似的事情:
1 2 3 | int16_t a = -32768; int16_t b = -1; int16_t c = a / b; |
在这种情况下,由于带符号整数的二进制补码表示形式,因此无法在带符号的16位整数中表示正32768,因此除法运算会溢出,从而导致-32768的错误值。
几个问题:
1)与本文所说的相反,以上内容并未引起硬件异常。我正在使用运行Linux的x86_64机器,当我除以零时,程序以
2)为什么除硬件运算如此严重地处理除法错误,而不是其他算术溢出?为什么硬件应该默默地忽略乘法溢出(很有可能偶然发生),但是应该认为除法溢出会触发致命中断?
===========编辑==============
好的,谢谢大家的回答。我得到的答复基本上是说上述16位整数除法不会引起硬件故障,因为商仍然小于寄存器大小。我不明白在这种情况下,存储商的寄存器为16位-太小而无法存储有符号正数32768。那么为什么不引发硬件异常呢?
好的,让我们直接在GCC内联汇编中执行此操作,看看会发生什么:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | int16_t a = -32768; int16_t b = -1; __asm__ ( "xorw %%dx, %%dx;" // Clear the DX register (upper-bits of dividend) "movw %1, %%ax;" // Load lower bits of dividend into AX "movw %2, %%bx;" // Load the divisor into BX "idivw %%bx;" // Divide a / b (quotient is stored in AX) "movw %%ax, %0;" // Copy the quotient into 'b' :"=rm"(b) // Output list :"ir"(a),"rm"(b) // Input list :"%ax","%dx","%bx" // Clobbered registers ); printf("%d ", b); |
这只是输出一个错误值:
在C语言中,永远不会在小于
在汇编代码中,由于将
您对
如果要在
例如,在我的实验(不是GCC)中,此代码
1 2 3 4 5 6 | __asm { mov AX, -32768 cwd mov BX, -1 idiv BX } |
导致预期的异常,因为它确实试图将
当您得到带有整数2的补码加/减/乘的整数溢出时,您仍然可以获得有效的结果-它只是缺少一些高阶位。此行为通常很有用,因此不适合为此生成异常。
但是,对于整数除法,除以零的结果是无用的(因为与浮点数不同,2的补码整数没有INF表示)。
在有关整数溢出的相关部分中:
Unlike the add, mul, and imul
instructions, the Intel division
instructions div and idiv do not set
the overflow flag; they generate a
division error if the source operand
(divisor) is zero or if the quotient
is too large for the designated
register.
在现代平台上,寄存器的大小为32位或64位; 32768将适合这些寄存器之一。但是,以下代码很可能会引发整数溢出执行(它在VC8的我的双核笔记本电脑上执行):
1 2 3 | int x= INT_MIN; int y= -1; int z= x/y; |
Contrary to what this article says, the above did NOT cause a hardware exception
文章没有这么说。是说
... they generate a division error if the source operand (divisor) is zero or if the quotient is too large for the designated register
寄存器大小肯定大于16位(32 || 64)
我猜想在某些旧计算机上,尝试被零除会导致一些严重的问题(例如,将硬件置于一个无穷循环中,试图进行足够的减法,以便在操作员进行修复之前,剩余的余数小于红利。 ),这开始了除数溢出的传统,被认为比整数溢出更严重的故障。
从编程的角度来看,没有理由认为意外的除法溢出应该比意外的整数溢出(有符号或无符号)更严重。考虑到划分的成本,事后检查溢出标志的边际成本将很小。传统是我看到具有硬件陷阱的唯一原因。
在使用32位
如果您想尝试引发异常(实际上仍然可能会发生或可能不会发生,这取决于实现),请尝试:
1 | int a = INT_MIN, b = -1, c = a/b; |
您可能需要做一些技巧,以防止编译器在编译时对其进行优化。
您的示例未生成硬件异常的原因是由于C的整数提升规则。小于
至于为什么对不同类型的溢出进行不同处理的原因,请考虑在x86机器级别上,确实没有乘法溢出的事情。当您将AX与其他寄存器相乘时,结果进入DX:AX对,因此结果始终有空间,因此没有机会发出溢出异常信号。但是,在C语言和其他语言中,两个