如果你查看 x86 的各种乘法指令,只查看 32 位变体而忽略 BMI2,你会发现这些:
-
imul r/m32 (32x32->64 有符号乘法)
-
imul r32, r/m32 (32x32->32 乘) *
-
imul r32, r/m32, imm (32x32->32 乘) *
-
mul r/m32 (32x32->64 无符号乘法)
请注意,只有“加宽”乘法具有无符号对应项。中间用星号标记的两种形式都是有符号和无符号乘法,因为对于没有得到额外“上部”的情况,这是一回事。 p>
“加宽”乘法在 C 中没有直接等价物,但编译器可以(并且经常)使用这些形式。
例如,如果你编译这个:
uint32_t test(uint32_t a, uint32_t b)
{
return a * b;
}
int32_t test(int32_t a, int32_t b)
{
return a * b;
}
使用 GCC 或其他一些相对合理的编译器,你会得到这样的结果:
test(unsigned int, unsigned int):
mov eax, edi
imul eax, esi
ret
test(int, int):
mov eax, edi
imul eax, esi
ret
(使用 -O1 的实际 GCC 输出)
因此,符号对于乘法(至少对于您在 C 中使用的那种乘法)和其他一些操作而言并不重要,即:
- 加法和减法
- 按位与、或、异或、非
- 否定
- 左移
- 比较平等
x86 不提供单独的签名/未签名版本,因为无论如何都没有区别。
但是对于某些操作是有区别的,例如:
- 师(
idiv vs div)
- 余数(还有
idiv 与div)
- 右移(
sar vs shr)(但要注意 C 中的有符号右移)
- 比较大于/小于
但最后一个很特殊,x86 也没有单独的签名和未签名版本,而是有一个操作(cmp,实际上只是一个非破坏性的sub),可以同时执行这两个操作,并给出几个结果(“标志”中的多个位受到影响)。稍后实际使用这些标志(分支、条件移动、setcc)的指令然后选择他们关心的标志。比如,
cmp a, b
jg somewhere
如果a 的“签名大于”b,则将转到somewhere。
cmp a, b
jb somewhere
如果a 是“下方未签名”b,则会转到somewhere。
有关标志和分支的更多信息,请参阅Assembly - JG/JNLE/JL/JNGE after CMP。
这不会是有符号和无符号乘法相同的正式证明,我只是尝试让您了解为什么它们应该相同。
考虑 4 位 2 的补码整数。它们各个位的权重是从 lsb 到 msb、1、2、4 和 -8。当您将其中两个数字相乘时,您可以将其中一个数字分解为与其位相对应的 4 个部分,例如:
0011 (decompose this one to keep it interesting)
0010
---- *
0010 (from the bit with weight 1)
0100 (from the bit with weight 2, so shifted left 1)
---- +
0110
2 * 3 = 6 所以一切都检查出来了。这只是大多数人在学校学习的常规长乘法,只有二进制,这使它更容易,因为你不必乘以十进制数字,你只需乘以 0 或 1,然后移位。
不管怎样,现在取一个负数。符号位的权重为 -8,因此在某一时刻,您将生成部分乘积 -8 * something。乘以 8 是左移 3,所以前一个 lsb 现在是 msb,所有其他位都是 0。现在如果你否定它(它毕竟是 -8,而不是 8),什么也不会发生。零显然没有变化,但 8 也是如此,而且通常只有 msb 集的数字:
-1000 = ~1000 + 1 = 0111 + 1 = 1000
因此,如果 msb 的权重为 8(如无符号情况)而不是 -8,那么您已经做了同样的事情。