理解汇编语言的mul和imul指令的问题

cla*_*aws 8 x86 assembly nasm

我正在通过paul caurterPC Assembly学习80386

  mul source
Run Code Online (Sandbox Code Playgroud)
  • 如果操作数是字节大小,则将其乘以AL寄存器中的字节,结果存储在AX的16位中.

精细.

  • 如果源是16位,则将其乘以AX中的字,32位结果存储在DX:AX中.

Q1:为什么选择DX:AX?为什么它不能存储在EAX/EDX中?

imul 真是令人困惑

imul dest, source1
imul dest, source1, source2
Run Code Online (Sandbox Code Playgroud)

alt text http://img697.imageshack.us/img697/8976/imul.gif

我在理解表格方面遇到了问题.

Q2:在表的第2个条目中.再次,为什么DX:AX.为什么不EAX或EDX?

现在考虑以下代码片段:

imul eax ; edx:eax = eax * eax
mov ebx, eax ; save answer in ebx
mov eax, square_msg ; square_msg db "Square of input is ", 0
call print_string ; prints the string eax
mov eax, ebx 
call print_int ;  prints the int stored in eax
call print_nl ; prints new line
Run Code Online (Sandbox Code Playgroud)

Q3:它预先说过,The notation EDX:EAX means to think of the EDX and EAX registers as one 64 bit register with the upper 32 bits in EDX and the lower bits in EAX.所以答案也存放在edx中,对吧?在上面的代码中我们没有考虑任何EDX,我们只是指EAX如何仍然有效?

问题4:我对表中所有条目的其余部分都有疑问.两个n位数(n = 8/16/32位)的最坏情况乘法结果是2n位.为什么它将两个16/32位乘法的结果存储在相同大小的寄存器本身?

Gre*_*ill 8

Q1/Q2:x86指令集保持其16位历史记录.进行16位乘法时,答案存储在DX:AX中.这就是它的方式,因为它就是16位的土地.

问题3:如果您尝试计算大于2 ^ 16的数字的平方,则显示的代码存在错误,因为代码忽略了存储的结果的高32位edx.

问题4:我想你可能会误读表格.8位乘法存储在16位结果中; 16位乘法存储在32位结果中; 32位乘法存储在64位结果中.你指的是哪一行?


Nil*_*nck 6

imul指令有很多不同的变化。2位代码。

您偶然发现的变量是16位乘法。它将AX寄存器与作为imul参数传递的任何值相乘,并将结果存储在DX:AX中。

一个32位变体的工作方式类似于16位乘法,但是将寄存器写入EDX:EAX。要使用此变体,您要做的就是使用32位参数。

例如:

  ; a 16 bit multiplication:
  mov ax, [factor1]
  mov bx, [factor2]
  imul bx              ; 32-bit result in DX:AX
  ; or  imul  word [factor2]

  ; a 32 bit multiplication:
  mov eax, [factor1]
  mov ebx, [factor2] 
  imul ebx             ; 64-bit result in EDX:EAX
Run Code Online (Sandbox Code Playgroud)

在386或更高版本上,您还可以编写imul两个操作数形式的。这使得它更加灵活和易于使用。在该变体中,您可以自由选择任意两个寄存器作为源和目标,并且CPU不会浪费时间在任何地方写入上半部分的结果。并且不会破坏EDX。

  mov   ecx, [factor1]
  imul  ecx, [factor2]    ; result in ecx, no other registers affected
  imul  ecx, ecx          ; and square the result
Run Code Online (Sandbox Code Playgroud)

或为带符号的16位输入匹配您的imul。(将movzx用于无符号输入)

  movsx   ecx, word [factor1]
  movsx   eax, word [factor2]  ; sign-extend inputs to 32-bit
  imul    eax, ecx             ; 32-bit multiply, result in EAX
  imul    eax, eax             ; and square the result
Run Code Online (Sandbox Code Playgroud)

imul的此变体在386中引入,并提供16位和32位操作数大小。(以及64位模式下的64位操作数大小)。

在32位代码中,您始终可以假定有386条类似imul reg, reg/mem的指令可用,但是如果您不关心较旧的CPU,则可以在16位代码中使用它。

286引入了3操作数立即数形式。

imul  cx, bx, 123        ; requires 286

imul  ecx, ebx, 123      ; requires 386
Run Code Online (Sandbox Code Playgroud)


phu*_*clv 6

Q1/Q2:为什么是 DX:AX ?为什么它不能存储在 EAX / EDX 中?

就像其他人说的那样,这只是为了向后兼容。最初的(i)mul指令是从16位x86其来意的32位x86指令集出现过,所以他们不能把结果存储到EAX / EDX,因为没有E-寄存器

Q3:在上面的代码中,我们没有考虑任何 EDX 我们只是指 EAX 这如何仍然有效?

您输入了不会导致结果溢出的小值,因此您没有看到差异。如果您使用足够大的值(>= 16 位),您将看到 EDX != 0 并且打印结果将不正确。

Q4:它怎么把两个16/32位相乘的结果存储在本身相同大小的寄存器中?

并不是结果仍然与操作数的大小相同将两个 n 位值相乘总是产生一个 2n 位值。但是在imul r16, r/m16[, imm8/16]和它们的 32/64 位对应项中,高 n 位结果被丢弃。当您只需要结果的低 16/32/64 位(即非加宽乘法)时,或者当您可以确保结果不会溢出时,就会使用它们。

  • 双操作数形式——在这种形式下,目标操作数(第一个操作数)乘以源操作数(第二个操作数)。目标操作数是通用寄存器,源操作数是立即数、通用寄存器或内存位置。中间积(输入操作数的两倍)被截断并存储在目标操作数位置。
  • [... 三操作数形式相同]

https://www.felixcloutier.com/x86/IMUL.html

现在的现代编译器几乎只将多操作数imul用于有符号和无符号乘法,因为

  • 两种情况下的低位总是相同,并且在 C 中,两个变量相乘会生成相同大小的结果 ( intx int? int, longx long? long...),它非常适合imul的操作数。只有这样,才能强制编译器以发射单操作数mulimul使用类型寄存器大小的两倍
  • 看到乘法结果比寄存器大小更宽的情况非常罕见,int64_t a; __int128_t p = (__int128_t)a * b;因此(i)mul很少需要单操作数
  • 仅计算较低位将比获得整个结果更快。
  • 由于各种形式的imul教学 ,使用起来更加灵活
    • 在 2-operand 形式中,您不需要保存/恢复 EDX 和 EAX
    • 3 操作数形式进一步允许您进行非破坏性乘法
  • 现代 CPU 通常针对 的多操作数版本进行优化imul(因为现在现代编译器几乎只将多操作数imul用于有符号和无符号乘法),因此它们会比单操作数更快(i)mul