为什么没有实施 DIV 指令来设置 CF 而不是引发异常

Why wasn't DIV instruction implemented to set the CF instead of raising Exceptions

我知道在汇编中划分时必须非常小心,即。 这样做:

          mov ah, 10h
          mov al, 00h ; dividend = 1000h
          mov bl, 10h ; divisor = 10h
          div bl      ; Integer overflow exception, /result 100h cannot fit into al

我已经编写了一些可能不会引起误解的逻辑来为除法创建一个更友好的环境:

          mov ah, 10h
          mov al, 00h
          mov bl, 10h 
TryDivide:
          cmp bl,ah
          jna CatchClause
          div bl
          clc
          jmp TryEnd
CatchClause:
          stc
TryEnd:
     

有没有人知道像这样的东西没有实现的技术原因,我们有例外而不是标志集/寄存器被截断?

要获得明确的答案,您必须询问 8086 指令集的设计者 Stephen Morse

其他英特尔工程师致力于实际实现,但 apparently the ISA was designed on paper first, almost entirely by just one guy. He's also credited as principal architect of 8086. PC World interviewed him 在 2008 年,8086 诞生 30 周年之际,更重要的是他写了一本书,The 8086/8088 Primer (1982)。我没读过,但显然他讨论了一些设计决策以及如何对其进行编程。如果你幸运的话,也许他写了一些关于选择 div/idiv 陷阱的东西。


没有理由必须这样;设置 CF and/or OF 和截断将是有效的设计。但是您仍然需要选择一些值以在 divide-by-0 的情况下 1 中放入 quotient/remainder 输出寄存器。 (我认为对于具有硬件 division 的 ISA 来说,至少 divide 有一个 divide-by-error 异常是相当普遍的,但不幸的是 On which platforms does integer divide by zero trigger a floating point exception? 只提到 x86 作为带有陷阱的 ISA。如果 division 陷阱和 POSIX OS 交付一个信号,对于算术异常它必须是 SIGFPE。)

请注意,其他 ISA 确实会做出不同的选择。 例如 ARM division 从不出错,也不设置标志。 (虽然它不提供 ide 双宽度 dividend,所以只有 和 division by 0 cases 是特殊的。 )

IDK 如果构建一个硬件 divide 单元(或微码)可以得到溢出情况的正确截断商(当确切的商是 wider 比16 位)将比简单地检测溢出和退出更难。如果是这样,那将是一个很好的理由。

(在输出寄存器中留下垃圾并设置 FLAGS 是可能的,但不是很好;每个 division 如果想避免使用垃圾的可能性,都需要事后检查结果。)

注意 1:div 在某些方面是 0 的特例:high_half < divisor 对于 divisor=0 对于 any divide第二个。但是没有明确定义的数学结果可以截断。 IEEE FP division 通过将 divisor 接近 0(即 +- 无穷大)视为极限来解决此问题。但是应该假设整数 0 正好是 0,而不是某个小数,并且无论如何都没有带内 NaN 或 Inf 值可以使用,只有有限的 0xFFFF...


没有其他 8086 数学指令需要截断,如果你 consider FLAGS

请注意,8086 包括 mul and imul, which do widening multiply: DX:AX = AX * src. (CF and OF are set if the high half is non-zero (for mul), or if the high half isn't the sign-extension of the low half (for imul)). Only later CPUs introduced truncating forms like imul r, r/m, imm (186) 和 imul r, r/m (386) 的单操作数形式,不会浪费时间写入高一半在任何地方,尽管仍然设置 FLAGS 以便您可以根据需要检测签名包装。 (大多数用途不会,所以后来的 CPU 只提供 provided imul,mul 的版本除了 FLAGS 之外都是一样的。)

add/sub 可以 carry/borrow,但加法的完整结果可作为 CF : reg 获得,并在进位标志中添加额外的位。

如果您将ider sar / shr / shl reg, cl 作为按位逻辑运算而不是数学运算,那么它不算数,即使它可以移出多个位而不将它们留在任何地方. (最后一位留在 CF 中,因此可以通过循环进位来撤消移位 1。)

剩下 DIV / IDIV 因为我认为唯一可能有 wider 结果但无处可放的算术指令。 可能 是选择让他们犯错的部分动机。


high_half < divisor 无符号 division

的陷阱证明

这是运算数大小中商拟合的确切条件。 1:0(例如 0x0100 表示 8 位操作数大小)是 不适合 的最小商,因此 0x0100 * divisor 是最小的 divide然后会产生一个不适合 8 位的商。

dividend 拆分为 hi:lo 与 dividend 宽度相同的两半时 divisor:0 .

任何小于该值的数字都必须从高半部分“借用”,使其严格小于 divisor

(带符号的 division 也有 INT_MIN / -1 溢出极端情况,高半部分检查可能必须针对绝对值。)