C 语言有符号和无符号类型,如 char 和 int。 我不确定它是如何在汇编级别实现的,因为 例如,在我看来,有符号和无符号的乘法 会带来不同的结果,所以汇编都做无符号的 和带符号的算术或只有一个,这在某种程度上 模拟不同的情况?
最佳答案
如果你看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
与div
) - 剩余部分(也是
idiv
与div
) - 右移(
sar
vsshr
)(但要注意 C 语言中的有符号右移) - 比较大于/小于
但最后一个很特别,x86 也没有单独的有符号和无符号版本,而是只有一个操作(cmp
,它实际上只是一个非破坏性的 sub
) 同时执行这两项操作,并给出多个结果(“标志”中的多个位受到影响)。实际使用这些标志的后续指令(分支、条件移动、setcc
)然后选择它们关心的标志。例如,
cmp a, b
jg somewhere
如果 a
的“符号大于”b
,将去某处
。
cmp a, b
jb somewhere
如果 a
是“下面未签名的”b
,将去某处
。
参见 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,那么您所做的事情与您所做的相同。
关于c - x86 上的有符号和无符号算术实现,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25241477/