C++ 导致除法溢出错误(x86)
关于x86或x86_64体系结构上的除法溢出错误,我有几个问题。最近我一直在读关于整数溢出的文章。通常,当算术运算导致整数溢出时,会设置标志寄存器中的进位或溢出位。但是很显然,根据,除法运算产生的溢出不会设置溢出位,而是触发硬件异常,类似于被零除法 现在,由除法产生的整数溢出比乘法更为罕见。只有几种方法可以触发除法溢出。一种方法是采取如下措施:C++ 导致除法溢出错误(x86),c++,c,assembly,x86,integer-division,C++,C,Assembly,X86,Integer Division,关于x86或x86_64体系结构上的除法溢出错误,我有几个问题。最近我一直在读关于整数溢出的文章。通常,当算术运算导致整数溢出时,会设置标志寄存器中的进位或溢出位。但是很显然,根据,除法运算产生的溢出不会设置溢出位,而是触发硬件异常,类似于被零除法 现在,由除法产生的整数溢出比乘法更为罕见。只有几种方法可以触发除法溢出。一种方法是采取如下措施: int16_t a = -32768; int16_t b = -1; int16_t c = a / b; 在这种情况下,由于二者是有符号整数的补码
int16_t a = -32768;
int16_t b = -1;
int16_t c = a / b;
在这种情况下,由于二者是有符号整数的补码表示,您不能在有符号16位整数中表示正32768,因此除法运算溢出,导致错误值-32768
有几个问题:
1) 与本文所述相反,上述情况并没有导致硬件异常。我使用的是一台运行Linux的x86_64机器,当我除以0时,程序终止,出现浮点异常。但当我导致除法溢出时,程序照常继续,默默地忽略错误的商。那么为什么这不会导致硬件异常呢
2) 为什么硬件对除法错误的处理如此严格,而不是其他算术溢出?为什么硬件应该默默地忽略乘法溢出(更可能意外发生),而除法溢出应该触发致命中断
=============编辑==============
好的,谢谢大家的回复。我得到的回答基本上是,上面的16位整数除法不应该导致硬件故障,因为商仍然小于寄存器大小。我不明白。在这种情况下,存储商的寄存器是16位-太小,无法存储带符号的正32768。那么为什么没有引发硬件异常呢
好的,让我们直接在GCC内联汇编中执行此操作,看看会发生什么:
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\n", b);
这只是输出一个错误的值:-32768
。尽管存储商(AX)的寄存器太小,无法容纳商,但仍然没有硬件例外。所以我不明白为什么这里没有出现硬件故障 当整数2的补码加/减/乘得到一个整数溢出时,仍然有一个有效的结果-它只是缺少一些高阶位。这种行为通常很有用,因此不适合为此生成异常
然而,对于整数除法,被零除的结果是无用的(因为,与浮点不同,2的补码整数没有INF表示)
与本文所说的相反,上述情况并没有导致硬件异常
文章没有这样说。他说
。。。如果源操作数(除数)为零或商对于指定的寄存器太大,则会生成除法错误
寄存器大小肯定大于16位(32 | | 64):
与add、mul和imul不同
指令,英特尔部门
未设置div和idiv指令
溢出标志;它们产生了一个
如果源操作数无效,则出现除法错误
(除数)为零或如果商
太大,不适合指定的
登记
寄存器的大小在现代平台上为32位或64位;32768将装入其中一个寄存器。但是,下面的代码很可能会抛出一个整数溢出执行选项(它在我的VC8 core Duo笔记本电脑上执行):
在C语言中,算术运算永远不会在小于int
的类型中执行。每当您尝试对较小的操作数进行算术运算时,它们首先会受到整数提升的影响,从而将它们转换为int
。如果在您的平台上int
是32位宽,那么就没有办法强制C程序执行16位除法。编译器将生成32位除法。这可能就是为什么你的C实验没有产生预期的除法溢出。如果您的平台确实有32位的int
,那么最好的选择是对32位操作数进行同样的尝试(即将int\u MIN
除以-1
)。我非常确信,通过这种方式,即使在C代码中,您最终也能够重现溢出异常
在汇编代码中使用16位除法,因为您指定了BX
作为idiv
的操作数。x86上的16位除法将存储在DX:AX
对中的32位被除数除以idiv
操作数。这就是您在代码中所做的。DX:AX
对被解释为一个复合32位寄存器,这意味着这对中的符号位现在实际上是DX
的最高阶位。AX的最高阶位不再是符号位
你用DX做了什么?你只是清除了它。您将其设置为0。但是当DX
设置为0时,您的红利被解释为正!从机器的角度来看,这样的DX:AX
对实际上代表一个正值+32768
。也就是说,在汇编语言实验中,你将+32768
除以-1
。结果应该是-32768
。这里没什么不寻常的
如果要在DX:AX
对中表示-32768
,则必须对其进行符号扩展,即必须用所有一位模式而不是零填充DX
。您应该先用-32768
初始化AX
,然后执行cwd
,而不是执行xor DX、DX
。这将使符号扩展到AX
到DX
例如,在我的实验中(
int x= INT_MIN;
int y= -1;
int z= x/y;
__asm {
mov AX, -32768
cwd
mov BX, -1
idiv BX
}
int a = INT_MIN, b = -1, c = a/b;