Assembly 我们何时以及为什么与mul/div签订扩展和使用干熄焦协议?

Assembly 我们何时以及为什么与mul/div签订扩展和使用干熄焦协议?,assembly,x86,division,integer-division,sign-extension,Assembly,X86,Division,Integer Division,Sign Extension,今天我做了一个测试,唯一不懂的问题是把一个双字转换成四字 这让我想,为什么/什么时候我们用符号扩展乘法或除法?此外,我们什么时候使用像cdq这样的指令?使用/for签名32位/32位=>32位除法, xor edx,edx/div用于未签名。 以EAX中的被除数开始,除数指定为DIV或IDIV的操作数 mov eax, 1234 mov ecx, 17 cdq ; EDX = signbit(EAX) idiv ecx

今天我做了一个测试,唯一不懂的问题是把一个双字转换成四字

这让我想,为什么/什么时候我们用符号扩展乘法或除法?此外,我们什么时候使用像cdq这样的指令?

使用/for签名32位/32位=>32位除法,
xor edx,edx
/
div
用于未签名。

以EAX中的被除数开始,除数指定为DIV或IDIV的操作数

   mov  eax, 1234
   mov  ecx, 17
   cdq                   ; EDX = signbit(EAX)
   idiv  ecx             ; EAX = 1234/17     EDX = 1234%17
如果将EDX/RDX归零,而不是将符号延伸到EDX:EAX,则在
idiv
之前

使用64/32位=>32位除法的“满幂”是可能的,但除非知道除数足够大,所以商不会溢出,否则就不安全。(也就是说,通常不能仅使用
mul
/
div
和EDX:EAX中的64位临时值来实现
(a*b)/c

除法在商溢出时引发异常(#DE)。在Unix/Linux上,用于算术异常,包括除法错误。对于正常符号或零扩展除法,溢出仅可能发生(即2的补码是最负数的特例)


正如您可以从insn ref手册(标记wiki中的链接)中看到的:

  • 一个操作数
    mul
    /
    imul
    edx:eax=eax*src
  • 两个操作数
    imul
    dst*=src
    。e、 g.
    imul ecx、esi
    不读取或写入eax或edx

  • div
    /
    idiv
    :用src除以
    edx:eax
    eax中的商
    edx中的余数
    。没有任何形式的
    div
    /
    idiv
    忽略输入中的
    edx
  • 符号将
    eax
    扩展到
    edx:eax
    ,即将
    eax
    的符号位广播到
    edx
    的每一位。不要与
    cdqe
    混淆,64位指令是
    movsxd-rax,eax
    的一种更紧凑的形式

    最初(8086),只有
    cbw
    ax=sign\u extend(al)
    )和
    cwd
    dx:ax=sign\u extend(ax)
    )。x86到32位和64位的扩展使得助记符有点模棱两可(但请记住,除了
    cbw
    ,eax内部版本的扩展总是以
    e
    结尾)。没有dl=sign_位(al)指令,因为8位mul和div是特殊的,并且使用
    ax
    而不是
    dl:al


由于
[i]mul
的输入是单寄存器,因此在乘法之前,不需要对
edx进行任何操作

如果您的输入是有符号的,您可以对其进行符号扩展,以填充用作乘法器输入的寄存器,例如使用
movsx
cwde
eax=sign\u extend(ax)
)。如果输入是无符号的,则进行零扩展。(例外情况是,如果您只需要乘法结果的低16位,例如,)


对于除法,您始终需要将eax扩展为edx,并对其进行零或符号化。零扩展与edx的无条件零化相同,因此没有专门的说明。只需
xor edx,edx

cdq
之所以存在,是因为它比
mov-edx,eax
/
sar-edx,31
短得多,可以将eax的符号位广播到edx中的每一位。此外,立即计数大于1的移位直到186才存在,而且每次计数仍为1个周期,因此在8086上,您必须执行更糟糕的操作(例如分支,或将符号位旋转到底部并隔离+
neg
it)。因此,8086中的
cwd
在需要时节省了大量的时间/空间


在64位模式下,将32位值扩展到64位的符号和零是常见的。ABI允许在64位寄存器的高32位(包含32位值)中存在垃圾,因此如果您的函数只查看
edi
的低32位,则不能仅使用
[array+rdi]
对数组进行索引


因此,您会看到大量的
movsx rdi、edi
(符号扩展)或
mov eax、edi
(零扩展,是的,使用不同的目标寄存器更有效,因为英特尔mov消除不适用于
mov same,same

对不起-我总是混淆汇编中的除法,因为我对寄存器感到困惑。我认为被除数总是放在eax/ax中,而单操作数指令就是div/idivebx(或任何寄存器)。这将有效地执行eax/ebx,商数在eax中,余数在edx中。我的检查显示我们在调用包含71的EAX上的idiv和包含-4的另一个寄存器之前使用了cdq。为什么会这样?我们使用了每个寄存器的全部,我不明白为什么我们需要其中一个寄存器是四字。@Koronakesh:请阅读我答案的第一行和/或英特尔的insn ref手册<代码>idiv ebx
不执行
eax=(edx:eax)/ebx
eax=(edx:eax)%ebx
edx
始终是被除数的高一半,显式操作数始终是除数。没有任何形式的
div
/
idiv
会忽略
edx
的方式,即
imul
的2操作数和3操作数形式只产生一个寄存器结果。好的-这现在是有意义的。与除数相比,股息的大小是否有要求?另外,像cdq这样的指令存在仅仅是因为它比sub-edx、edx这样的指令低1字节吗?@Koronakesh:
cdq
存在是因为它比
mov-edx、eax
/
sar-edx、31
短得多,可以将eax的符号位广播到edx中的每一位
xor-edx,edx
0扩展,这与符号扩展不同。而且,计数大于1的移位直到286年才存在,所以需要一个循环是非常可怕的。至于尺寸限制,是的,如果你