获取C ++积分变量i
,并假设您将其值乘以2.
如果i
有签名,我相信该操作在某种程度上是等效的,至少在数学上,对于:
i = i << 1;
但是如果i
的类型是无符号的,那么因为无符号值不会溢出但是以模块的范围执行,大概是操作是这样的:
i = (i << 1) & (decltype(i))-1;
现在,我认为实际的机器指令可能比乘法的一系列移位更简洁。但是现代的,比如x86,CPU是否会对unsigned / modulo数学有特定的指令?或者,与带有符号值的数学相比,使用无符号值执行数学运算往往会花费额外的指令?
(是的,在编程时关心这一点会很荒谬;出于纯粹的好奇心,我很感兴趣。)
答案 0 :(得分:11)
不,它不需要更多指令,至少在x86上。
对于某些操作(例如加法和减法),相同的指令用于有符号和无符号类型。这是可能的,因为当使用带符号值的2的补码表示时它们的工作方式相同。
左移也没有区别:硬件只丢弃最左边的位,在你的例子中不需要按位执行。
对于其他操作(例如右移),有单独的指令:无符号值的SHR(右移)和有符号值的SAR(移位算术右),保留符号位。
还有单独的有符号/无符号乘法和除法指令:MUL / IMUL和DIV / IDIV,其中IMUL和IDIV用于有符号值。
答案 1 :(得分:11)
正如其他人已经写过的那样:对CPU来说无关紧要。有符号和无符号指令需要相同的时间,无符号算术中的某些操作更容易做,并且可能需要比带符号变量小的周期(多精度除法就是一个例子)。
然而,这只是故事的一半。
C ++将带符号的整数溢出定义为未定义的行为,将无符号整数定义为modulo2。这提供了完全不同的优化机会,导致不同的代码。
一个例子:
int foo (int a)
{
return a * 1000 / 500;
}
unsigned bar (unsigned a)
{
return a * 1000 / 500;
}
这里的foo可以优化为:
int foo (int a)
{
return a * 2;
}
酒吧会保持不变。
请注意,数学上这两个函数是相同的,但如果参数超过INT_MAX / 1000,它们会开始给出不同的结果。
由于签名溢出的影响未定义,编译器可以选择在简化表达式时假装没有INT_MAX。对于无符号算术,情况并非如此,编译器必须发出执行乘法和除法的代码。这当然比优化的变体慢。
注意:大多数编译器在进行此类优化时都是保守的,只有在您要求它们时才启用它们,因为它们往往会破坏代码和溢出检查。其他编译器,尤其是嵌入式和DSP世界中的编译器,即使在低优化级别也始终进行这些优化。为这类机器编写的程序员都知道细微的细节,所以这很少成为问题。
OTOH我们已经讨论了C / C ++程序员在stackoverflow上不止一次陷入此陷阱的故事。
答案 2 :(得分:2)
假设环绕溢出,这是两个补码硬件上的大多数(全部?)CPU算术指令无论如何,<<
对于无符号类型等同于乘法。因此,唯一的问题是,当您使用比用于包含它的寄存器小的类型进行算术运算时。
有关在算术表达式中至少提升int
(或unsigned int
)的规则,其设计目的是为了避免这种情况发生:当你将unsigned short
乘以2时结果是int
(如果unsigned int
或short
大小相同,则为int
。无论哪个,当寄存器大小与类型匹配时,不需要采用任何模数。使用二进制补码,无论如何都不需要有符号和无符号C ++乘法的不同指令:除非硬件提供溢出位并且您关心它的值(-1 * 2将是无符号溢出,但不是有符号溢出,即使结果位模式相同)。
可能需要掩码的唯一时间是/当您将结果转换回unsigned short
时。即便如此,我认为实施有时可以在用于保存中间int
值的unsigned short
大小的寄存器的顶部留下额外的“不相关”位。您知道那些额外的顶部位不会影响加法,乘法或减法的模数结果,并且如果将值存储到存储器中它们将被屏蔽掉(假设一条指令存储{{1的底部2个字节)大小寄存器到2个字节的内存,模数基本上是免费的)。因此,在分割,右移,比较以及我忘记的任何其他内容之前,必须小心掩盖实现,否则使用适当的指令(如果可用)。
答案 3 :(得分:1)
据我所知,大多数CPU都在硬件中拥有未签名的操作,我很确定x86会这样做。
答案 4 :(得分:1)
无符号数学确实溢出,因此隐含地模数它们各自的范围。
答案 5 :(得分:1)
interjay的答案涵盖了基础知识。更多细节:
现在,我认为实际的机器指令可能比乘法的一系列移位更简洁。
这取决于处理器。在过去,当晶体管价格昂贵时,像6500和6800这样的处理器的指令只能一次向左或向右移动一位。
后来,当芯片变大并且参数的操作码中有更多位时,实现了“桶形移位器”,它可以在一个周期内移位任意数量的位。这就是现代CPU的用途。
或者,与带有符号值的数学相比,使用无符号值执行数学运算往往会花费额外的指令吗?
从不。当无符号和有符号的操作不同时,每个操作都会有单独的指令。
答案 6 :(得分:1)
我认为你的方法是错误的:在无符号数据类型上,位移完全与它在锡上所说的完全相同,未占用的位用零填充。这会导致对左移的类型值进行正确的模运算操作,即乘法。右移没有算术类比,因为Z / nZ一般不是一个除法环,也没有除法概念;右移只是截断分裂。
另一方面, signed 类型存在歧义,因为有不同的方法将位模式解释为有符号整数。通过左移2的补码,你将获得乘法的预期“环绕”,但没有规范的右移行为选择。在旧的C标准中,我认为这是完全实现定义的,但我认为C99使这种行为具体化。