为什么没有实现DIV指令来设置CF而不是引发异常
我知道在组装时必须非常小心,即这样做:
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。
其他英特尔工程师致力于实际实现,但显然 ISA 是首先在纸上设计的,几乎完全由一个人设计。他还被誉为 8086 的首席架构师。 PC World在 2008 年 8086 30 周年之际采访了他,更重要的是他写了一本书,The 8086/8088 Primer (1982)。我没有读过它,但显然他讨论了一些设计决策以及如何对其进行编程。如果你很幸运,也许他写了一些关于选择有 div/idiv 陷阱的东西。
没有理由必须这样;设置 CF 和/或 OF 和截断本来是有效的设计。但是您仍然需要选择一些值来放入被零除的情况1中的商/余数输出寄存器。(我认为对于具有硬件除法的 ISA 来说,至少除以零的除法异常是相当普遍的,但是在哪些平台上整数除以零会触发浮点异常?不幸的是,只提到 x86 作为具有陷阱。如果除法确实陷阱,并且 POSIX 操作系统完全传递了信号,则对于算术异常,它必须是 SIGFPE。)
请注意,其他 ISA 确实会做出不同的选择。例如,ARM 部门从不出错,也不设置标志。(虽然它不提供双宽度红利,所以只有有INT_MIN / -1符号溢出和除以 0 的情况是特殊的。)
IDK 如果构建一个硬件除法单元(或微码)可以获得正确截断的溢出情况的商(当确切的商大于 16 位时)将比简单地检测溢出和救助更困难。如果是这样,那将是一个相当好的理由。
(在输出寄存器中留下垃圾并设置 FLAGS 是可能的,但不是很好;如果想要避免使用垃圾的可能性,每个除法都需要在事后检查结果。)
注 1:在某些方面除以 0 是一个特例:high_half < divisor对于任何被除数,除数为 0 时为假。但是没有定义明确的数学结果可以截断。IEEE FP 除法通过在除数接近 0(即 +-无穷大)时将其视为极限来解决此问题。但是整数 0 应该被假定为正好是 0,而不是一些很小的数字,并且无论如何都没有带内 NaN 或 Inf 值可以使用,只有一个有限的 0xFFFF ...
如果您考虑 FLAGS,则无需截断其他 8086 数学指令
请注意,8086仅包括muland的单操作数形式imul,它们会扩大乘法:DX:AX = AX * src。(如果高半部分不为零(对于mul),或者如果高半部分不是低半部分的符号扩展(对于),则设置 CF 和 OF imul)。只有后来的 CPU 引入了像imul r, r/m, imm( 186 ) 和imul r, r/m(386) 这样的截断形式,它们不会浪费时间在任何地方写入高半部分,尽管仍然设置 FLAGS 以便您可以根据需要检测签名换行。(大多数用途都没有,所以后来的 CPU 只提供 imul,除了 FLAGS 之外,mul 的版本是相同的。)
add/sub可以进位/借位,但CF : reg与进位标志中的额外位一样,可以使用加法的完整结果。
如果您将 sar / shr /shl reg, cl视为按位逻辑运算,而不是数学运算,那么即使它可以移出多个位而不将它们留在任何地方,它也不算数。(最后一位保留在 CF 中,因此可以通过循环进位来撤消移位 1。)
这留下了 DIV / IDIV,因为我认为这是唯一可以产生更广泛结果而无处可放的算术指令。 这可能是选择让他们过错的部分动机。
high_half < divisor 是无符号除法的证明
这是操作数大小中商拟合的确切条件。 1:0(例如,0x0100对于 8 位操作数大小)是不适合的最小商,因此0x0100 * divisor会产生不适合 8 位的商的最小被除数也是如此。
该红利是divisor:0分成与红利宽度相同的 hi:lo 一半。
任何小于该数字的数字都必须从高半部分“借用”,使其严格小于divisor.
(有符号除法也有INT_MIN / -1溢出边角情况,高半检查可能必须是绝对值。)