汇编语言中的MUL函数

27

我正在尝试在汇编中执行简单的乘法运算。然而,当调用MUL函数时,我并没有看到寄存器发生变化。

mov bx, 5    
mov cx, 10

mul cx

2
AX和DX将会改变,除非它们已经包含了结果。当指令没有按照你的期望执行时,请阅读手册。http://www.felixcloutier.com/x86/MUL.html - Peter Cordes
3
mov bx, 5 ==> mov ax, 5 - Weather Vane
它被称为“指令”。 - phuclv
相关:理解汇编语言的mul和imul指令方面存在的问题 / MUL指令不支持立即值 关于使用 imul bx, bx, 10 用一个常数(作为立即值)进行乘法运算。 - Peter Cordes
2个回答

72
这些叫做“指令”,它们指定处理器执行的操作。"mov"是"move"的助记符,而"mul"是"multiply"的助记符。其他常见的指令包括"add"、"sub"和"div"。我相信你可以想出这些操作的含义!
大多数指令都有两个参数,在技术术语中,它们通常被称为"操作数"。第一个(在左边)是"目标",第二个(在右边)是"源"。因此,在"mov bx, 5"的情况下,这将字面值"5"移动到目标寄存器"bx"中。当然,这些参数的顺序很重要,因为你不能将寄存器"bx"的内容移动到字面值"5"中!
"mul"指令有点奇怪,因为它的一些操作数是隐含的。也就是说,它们没有明确地指定为参数。对于"mul"指令,目标操作数被硬编码为"ax"寄存器。源操作数是作为参数传递的:它可以是寄存器或内存位置。
因此,你可以想象"mul cx"意味着"mul ax, cx",但你不这样写,因为"ax"目标寄存器是隐含的。
现在,"mul"指令命令处理器将目标操作数乘以源操作数,并将结果存储在目标操作数中。在代码中,你可以想象"mul cx"会转换成"ax = ax * cx"。现在你应该看到问题所在:你还没有初始化"ax"寄存器的内容,因此你正在将10(你放在"cx"中的值)乘以"ax"中留下的任意垃圾。因此,结果是没有意义的!
如果你确实想做5 * 10,那么你只需要改变代码中的一个字符:
mov  ax, 5     ; ax = 5
mov  cx, 10    ; cx = 10
mul  cx        ; ax = ax * cx   ; actually dx:ax = ax * cx
结果将存储在隐含目标寄存器ax中。
实际上,结果将存储在dx:ax中。这是一个寄存器对,意味着结果的高位部分将存储在dx中,而低位部分将存储在ax中。为什么会有这种额外的复杂性?因为两个16位值的乘积可能会导致一个大于16位的值!以一对16位寄存器的形式返回完整的乘积结果允许mul指令返回32位结果。不过当你刚开始学习时,你不需要担心这个。你可以忽略溢出的可能性,并从ax中提取结果的低位部分。(但请记住,16位mul会覆盖dx,无论你是否想要。在386和更高版本上,可以使用imul ax, cx来真正执行ax *= cx而不浪费时间写入dx。)
虽然我确信这只是一个玩具示例,但没有必要编写将两个常量相乘的代码。这可以在构建时完成,可以使用计算器硬编码该值,或者符号地写出常量的乘法并让汇编器进行计算。即mov ax, 50。或者使用mov ax, 5 * 10让汇编器为你完成它。但是,像我说的那样,我相信你已经知道了这个!
如果所有其他方法都失败,请参考给你麻烦的指令的文档。通过在谷歌中搜索指令名称和"x86",你几乎总能在线找到相关文档。例如,mul文档可以在这里以及其他一些站点找到。这些信息可能有点复杂,但只要努力一下,你就应该能够提取所需的信息。你还会在标签wiki中找到很多其他的优秀信息和链接。
引用块: 但由于某种原因,当MUL函数被标记时,我没有看到寄存器发生变化。
我还应指出,如果你正在使用调试器来逐步执行代码,则当前标记/高亮显示的行是即将执行的行。它还没有执行过,因此对其对寄存器、内存等的影响尚不可见。你必须跨过该指令,以便标记/高亮显示在下一行上,然后你将看到先前(刚执行的)指令的效果。
如果你理解了我上面的解释,那么在mul指令之后,你应该会看到axdx寄存器的内容发生变化。如果你的调试器显示了标志或指令指针,你还将看到它们发生变化。除此之外,没有任何其他的变化!(英特尔指令参考手册中mul入口没有列出机器

7
好的回答。值得一提的是,即使乘积足够小以适合于保存乘积的前两个寄存器之一,得到的乘积也会将高序寄存器清零。 - Andrew Hardiman

6
mul 指令有两个操作数:一个是指定的,另一个是隐含的。
当你写 mul cx 时,它的意思类似于:ax = ax * cx
实际上它的意思是 dx:ax = ax * cx - 全部 32 位乘积的高半部分总是被写入 dx。对于小乘积结果能“适合”在 ax 中的情况下,dx 将为零。如果你只想要结果的低 16 位,你可以认为它破坏了 dx1
回答你的问题,如果你真的想将 5 乘以 10,你可以这样做:
  mov  ax, 5   ; ax = 5
  mov  cx, 10  ; cx = 10

  mul  cx      ; dx:ax = ax * cx
; So   ax = ax * cx
; and dx=0 because the product happens to be small.

脚注 1:186支持 imul dst, src, constant,例如 imul ax, cx, 5,允许使用任何寄存器而不会隐式使用AX或DX。
386允许使用 imul reg,reg 进行非扩展乘法,没有隐式的寄存器使用。
https://www.felixcloutier.com/x86/imul 讨论了这两种形式。

当然,如果您可以使用386特性,则可以使用lea代替imul进行乘以3、5或9的操作,使用允许缩放索引的32位地址模式(即内置移位计数)。

  mov  ecx, 10
  lea  ax, [ecx + ecx*4]

1
如果产品大于16位,则完整的32位结果始终存储在dx:ax中,无论您是否需要。对于小结果,这意味着DX被清零。您的措辞暗示小结果会使DX未写入,但事实并非如此。为此,您需要386 imul ax,cx来执行非扩展乘法(仅使用16位低半部分)。或者186 mov cx,10 / imul ax,cx,5来使用立即源执行ax = cx * 5。(或者386 lea ax,[ecx + ecx * 4]更便宜地乘以5。) - Peter Cordes
谢谢您的评论。我会将其作为注释附加上去。 - Mostafa Wael
2
最好第一次就准确地描述它,而不是添加更正。我进行了一次编辑,我认为它同样易于阅读,但不暗示需要更正的任何内容。 - Peter Cordes

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接