导致除法溢出错误(x86)

Cha*_*l72 7 c c++ x86 assembly integer-division

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

现在,由除法产生的整数溢出比乘法更加罕见.只有几种方法可以触发除法溢出.一种方法是做一些事情:

int16_t a = -32768;
int16_t b = -1;
int16_t c = a / b;
Run Code Online (Sandbox Code Playgroud)

在这种情况下,由于有符号整数的二进制补码表示,您不能在带符号的16位整数中表示正32768,因此除法运算溢出,导致错误值-32768.

几个问题:

1)与本文所说的相反,上述内容并未导致硬件异常.我正在使用运行Linux的x86_64机器,当我除以零时程序终止于Floating point exception.但是当我导致除法溢出时,程序会像往常一样继续,默默地忽略错误的商.那么为什么这不会导致硬件异常呢?

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);
Run Code Online (Sandbox Code Playgroud)

这只是输出一个错误的值:-32768.即使存储商(AX)的寄存器太小而不适合商,仍然没有硬件异常.所以我不明白为什么这里没有引起硬件故障.

AnT*_*AnT 13

在C语言中,算术运算从不在小于的类型中执行int.每当你尝试对较小的操作数进行算术运算时,它们首先会进行积分促销,将它们转换为int.如果你的平台上int有32位宽,则无法强制C程序执行16位除法.编译器将生成32位除法.这可能就是为什么你的C实验不能产生预期的除法溢出的原因.如果您的平台确实有32位int,那么你最好的选择是尝试用32位操作数相同的事情(即除以INT_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,应该是.这里没什么不寻常的.

如果要-32768DX:AX对中进行表示,则必须对其进行符号扩展,即必须DX使用全一位模式而不是零填充.而不是xor DX, DX你应该AX用你的初始化-32768然后完成cwd.这将有迹象延伸AXDX.

例如,在我的实验(不是GCC)中这段代码

__asm  {
  mov AX, -32768
  cwd
  mov BX, -1
  idiv BX
}
Run Code Online (Sandbox Code Playgroud)

导致预期的异常,因为它确实试图分裂-32768-1.