C++ 导致除法溢出错误(x86)

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; 在这种情况下,由于二者是有符号整数的补码

关于x86或x86_64体系结构上的除法溢出错误,我有几个问题。最近我一直在读关于整数溢出的文章。通常,当算术运算导致整数溢出时,会设置标志寄存器中的进位或溢出位。但是很显然,根据,除法运算产生的溢出不会设置溢出位,而是触发硬件异常,类似于被零除法

现在,由除法产生的整数溢出比乘法更为罕见。只有几种方法可以触发除法溢出。一种方法是采取如下措施:

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;