我正在学习x86-64汇编与在线课程和课程是可怕的不清楚的细节。我已经在网上搜索并阅读了几个SO问题,但无法得到答案。
我试图弄清楚如何手工计算二进制乘法,但我被imul
卡住了。
给定这个二进制乘法的例子,11111111 * 00000011
,它可以被视为255 * 3
无符号或-1 * 3
有符号。255*3
mov al, 255
mov bl, 3
mul bl
这很简单,下面是我如何手工计算的,就像十进制乘法一样:
11111111
x 00000011
--------------
11111111
11111111
--------------
1011111101
结果溢出,上半部分是ah
中的10
,下半部分是al
中的11111101
。我的手工计算与程序结果相符。-1*3
当涉及到签名时,
mov al, -1
mov bl, 3
imul bl
编程结果是ah
中的11111111
和al
中的11111101
。
我怎样才能手工计算出这个结果呢?有人告诉我imul
中涉及到符号扩展,但我真的不知道它在这里是如何工作的。
我使用SASM IDE和NASM汇编。
3条答案
按热度按时间voase2hg1#
n x n => 2n
产品的lown
bits不依赖于有符号与没有签名,但高半部分有结果相当于将两个输入都符号扩展(imul
)或零扩展(mul
)到目标宽度,然后进行非加宽乘法,尽管实际实现当然会做一些更有效的事情。看看Erik的答案,看看它在比特方面是什么样子。如果我只是想手动计算
imul
会产生什么,我会执行-1 * 3 = -3
,然后将-3
的位模式计算为2的补码16位整数。因为它是imul
,我知道我必须将8位位模式解释为有符号-1
,而不是像mul
那样的无符号0xff
。我不会手动使用二进制,除非我正在检查一些使用乘法来移位和添加位字段的bithack(例如 Popcount assembly / sum indexes of set bits,但即使如此,我实际上也在考虑十六进制或字节。
在C语言中,
imul r/m8
是int16_t ax = (int16_t)(int8_t)al * (int16_t)(int8_t)src;
。(C语言很奇怪,因为像*
这样的运算符已经将窄输入提升到int
,而int
至少和int16_t
一样宽。在asm术语中,
movsx ax, al
(或cbw
);movsx cx, byte src
;imul ax, cx
,但不破坏CX。(如果你愿意,可以想象一个隐藏的临时寄存器。)您实际上很少需要扩展
mul
或imul
,因为它们在16位或更宽的输入上运行时超过1 uop(因为它们必须将乘积的一半写入多个寄存器,如EDX:EAX,而不仅仅是RAX的低16位)。有趣的是,8位imul bl
在现代CPU上作为单个uop运行。参见https://uops.info/和https://agner.org/optimize/但是
imul bl
只将其结果扩展到16位,而不是一直扩展到方便的32位,因此您通常希望将imul ecx, ebx
的输入扩展到32位,因为32-bit is the most efficient operand-size most of the time on x86-64。imul r, r/m
和imul r, r/m, imm
形式是单微操作,在现代CPU上速度很快。对于64位imul
,一些较旧或低功耗的CPU速度较慢。qqrboqgw2#
技术上讲,你在长乘法上走了捷径。虽然这种捷径对于获得无符号位模式是相当合理的,但它在概念上掩盖了一些东西。
对于未签名的,我们有:
它们的总和是17位,尽管从
mul
中只能得到16位,并且进位/无符号溢出不会发生,因为输入只有8位宽。在上面的例子中,我们添加了前导零,但将尾随零保留为空白。
而在有符号算术中:
它们的总和是17位,尽管从
imul
中只能得到16位,并且不会发生有符号溢出,因为输入只有8位宽。因此,我们通过将被乘数(第一个操作数)的符号扩展为16位部分乘积来说明被乘数的符号。
我们通过减去而不是添加最高有效部分乘积来说明乘数(第二个操作数)的符号。因为这是乘以0或
-2^7
,即该输入中符号位的位值。对于unsigned,所有的位值都是正的,但是n位2's complement的工作方式是给MSB一个位值-2^(n-1)
而不是+2^(n-1)
。例如,0x80
是-128
在8位2的补码(int8_t
)中的位模式,但+128作为无符号(uint8_t
)。或者,我们可以对输入进行符号扩展,并做16个部分积而不是8个,但这需要更多的工作。
o7jaxewo3#
老实说,我不能完全理解其他两个答案。对我来说太复杂了。我只需要一个愚蠢的,简单的,通用的规则。
我只想选适合我的。
其结果相当于将两个输入都符号扩展(imul)或零扩展(mul)到目标宽度,然后执行非加宽乘法
@Peter Cordes
要手动计算,有几种方法:您可以将两个输入符号扩展到16位,并执行16 × 16操作,仅保留低16位
@埃里克·艾德
我试过了,验证过了,这条规则对我很有效。
-1*3
,符号扩展
保持低16位,我得到正确的结果
11111111(ah) 11111101(al)
。尝试一个4位示例:
-2 * 3
,1110
imul0011
符号扩展
保留低8位,结果为
1111 1010
,-6
。以前我不确定sign extended在
imul
中是如何工作的,现在我明白了,很容易验证。顺便说一句,如果你发现手动计算对一些例子来说很乏味(例如4位-7 * -1
、1001 x 1111
、符号扩展1111 1001 x 1111 1111
,需要添加许多行),您可以使用Windows计算器(程序员模式)并快速验证结果。