【问题标题】:Why should EDX be 0 before using the DIV instruction?为什么在使用 DIV 指令之前 EDX 应该为 0?
【发布时间】:2017-11-05 12:44:33
【问题描述】:

我注意到当 EDX 包含一些像 00401000 这样的随机默认值时,我会使用这样的 DIV 指令:

mov eax,10
mov ebx,5
div ebx

它会导致整数溢出错误。但是,如果我将 edx 设置为 0 并执行相同的操作,它会起作用。我相信使用div 会导致商覆盖eax,余数覆盖edx

得到这个 INTEGER OVERFLOW ERROR 真的让我很困惑。

【问题讨论】:

  • 这就是指令的定义/实现方式——对于 32 位操作数,它将 edx:eax 中的 64 位值除以 32 位操作数值。
  • 您将 64 位数字除以 32 位数字,您必须在开始之前设置所有 64 位,否则您的结果将毫无意义。
  • 该链接来自FAQ section of the x86 tag wiki。对于处理 asm 的人来说,那里有很多好东西。
  • 在使用不熟悉的操作码之前,您最好先查看指令参考。 :)
  • 相关:8 位操作数大小是一种特殊情况:div bl (16b / 8b => 8b),使用 AX(不是 DL:AL)。 8086 assembly on DOSBox: Bug with idiv instruction?

标签: assembly x86 integer-division


【解决方案1】:

做什么

对于 32 位 / 32 位 => 32 位除法:将 32 位被除数从 EAX 零或符号扩展为 64 位 EDX:EAX。
对于 16 位,使用 cwd 或 xor-zeroing 将 AX 转换为 DX:AX。

  • 无符号:XOR EDX,EDX 然后DIV divisor
  • 签名:CDQ 然后IDIV divisor

另见When and why do we sign extend and use cdq with mul/div?


为什么(TL;DR)

对于DIV,寄存器EDXEAX 形成一个单独的64 位值(通常显示为EDX:EAX),然后除以EBX

所以如果EAX = 10 或十六进制AEDX20 或十六进制14,那么它们一起形成64 位值十六进制14 0000 000A 或十进制85899345930 .如果除以5,则结果为17179869186 或十六进制
4 0000 0002这是一个不适合 32 位的值

这就是整数溢出的原因。

但是,如果 EDX 只是 1,您可以将十六进制 1 0000 000A 除以 5,得到十六进制
3333 3335。这不是您想要的值,但不会导致整数溢出。

要真正将 32 位寄存器 EAX 除以另一个 32 位寄存器,请注意 EDX:EAX 形成的 64 位值的顶部是 0

因此,单个除法之前,一般应该将EDX 设置为0

(或者对于已签名的除法,cdqEAX 签名扩展为 EDX:EAXidiv 之前)


EDX 不一定总是0。结果导致溢出的可能性不大。

我的BigInteger 代码中的一个示例:

DIV 相除后,商为EAX,余数为EDX。要将 BigInteger(由许多 DWORDS 组成的数组)除以 10(例如将值转换为十进制字符串),您可以执行以下操作:

    ; ECX contains number of "limbs" (DWORDs) to divide by 10
    XOR     EDX,EDX      ; before start of loop, set EDX to 0
    MOV     EBX,10
    LEA     ESI,[EDI + 4*ECX - 4] ; now points to top element of array
@DivLoop:
    MOV     EAX,[ESI]
    DIV     EBX          ; divide EDX:EAX by EBX. After that,
                         ; quotient in EAX, remainder in EDX
    MOV     [ESI],EAX
    SUB     ESI,4        ; remainder in EDX is re-used as top DWORD... 
    DEC     ECX          ; ... for the next iteration, and is NOT set to 0.
    JNE     @DivLoop

在该循环之后,整个数组(即BigInteger)表示的值除以10EDX 包含该除法的余数。

FWIW,在我使用的汇编器(Delphi 的内置汇编器)中,以@ 开头的标签是函数的本地标签,即它们不会干扰其他函数中的同名标签。

【讨论】:

  • 这可能是对“为什么我的损坏的分隔代码不起作用”常见问题解答比我标记的副本更好的规范答案。我进行了编辑以修复语法错误,但还有另一个错误:ESI 在您的循环中保存了一个指针,因此JNE @DivLoop 不会退出循环,直到您将整个地址空间向下移动到ESI = (int*)4。也许您打算使用dec ecxcmp
  • FWIW,@PeterCordes:在我的汇编程序中,DIV EAX,EBXDIV EBX 都是允许的。但是由于在一个版本中,DIV EBX 生成了错误代码,而 DIV EAX,EBX 没有,所以我总是使用后者。
  • @PeterCordes:我没有逐字复制(我的代码使用展开的循环,而且要复杂得多)。我想我忘记了什么。我会修改的。
  • 啊,好吧,我想知道div eax, ebx 是如何在一个基于工作代码的示例中进入那里的! (是的,我可以说它一定不是逐字复制/粘贴的)。当您使用它时,我建议您发表评论以指出您有意不在循环内将 EDX 归零;前一个除法的余数是下一个分支输入的上半部分。 (这是故意的,对吧?这对 BigInteger 来说似乎是明智的)
  • @PeterCordes:我认为我在循环末尾的评论已经解释了这一点(即 EDX 没有归零)。
【解决方案2】:

DIV 指令将 EDX:EAX 除以 DIV 指令后面的 r/m32。因此,如果您未能将 EDX 设置为零,那么您使用的值会变得非常大。

相信会有所帮助

【讨论】:

    猜你喜欢
    • 2015-06-29
    • 1970-01-01
    • 1970-01-01
    • 2015-09-02
    • 2021-08-10
    • 1970-01-01
    相关资源
    最近更新 更多