C中的移位算子(<<,>>)是算术还是逻辑?
在C中,移位运算符( <<
, >>
)是算术还是逻辑?
根据K&R第二版 ,结果是有符号值右移的实现依赖。
维基百科说,C / C ++'通常'实现了有符号值的算术转换。
基本上你需要testing你的编译器或不依靠它。 我的VS2008帮助当前的MS C ++编译器说他们的编译器做算术转换。
左移时,算术和逻辑移位没有区别。 右移时,移位types取决于正在移位的值的types。
(作为不熟悉差异的读者的背景,“逻辑”右移1位将所有位向右移位,并用0填充最左位。“算术”移位将原始值留在最左位当处理负数时,差异变得重要)。
当移动无符号值时,C中的>>运算符是一个逻辑移位。 在移动有符号值时,>>运算符是一个算术移位。
例如,假设一个32位的机器:
signed int x1 = 5; assert((x1 >> 1) == 2); signed int x2 = -5; assert((x2 >> 1) == -3); unsigned int x3 = (unsigned int)-5; assert((x3 >> 1) == 0x7FFFFFFD);
移
首先是math观点的逻辑和算术变换之间的差异,而不用担心数据types的大小。 逻辑移位总是用零填充丢弃位,而算术移位用零填充仅用于左移,但是对于右移它复制MSB,从而保留操作数的符号(假设二进制补码编码为负值)。
换句话说,逻辑移位将移位后的操作数视为一个位stream,并移动它们,而不会打扰生成的值的符号。 算术转换把它看作一个(带符号的)数字,并保留这个符号。
数字X乘以n的左算术移位相当于将X乘以2 n ,因此相当于逻辑左移; 一个合乎逻辑的转变也会给出同样的结果,因为MSB无论如何都落在了最后,没有什么可以保留的。
如果X是非负的,则X乘以n的右算术移位等于X的整数除以2 n ! 整数除法是除了math分割和0( trunc )。
对于由二进制补码表示的负数,向右移位n位具有math上除以2 n并朝-∞( 落地 )四舍五入的效果; 因此,对于非负值和负值,右移是不同的。
对于X≥0,X >> n = X / 2n = trunc(X÷ 2n )
对于X <0,X >> n = floor(X÷2 n )
其中÷
是math除法, /
是整数除法。 我们来看一个例子:
37) 10 = 100101) 2
37÷2 = 18.5
37/2 = 18(对0的舍入18.5)= 10010) 2 [算术右移的结果]
-37) 10 = 11011011) 2 (考虑二进制补码,8位表示)
-37÷2 = -18.5
-37 / 2 = -18(向0取整18.5)= 11101110) 2 [不是算术右移的结果]
-37 >> 1 = -19(对-∞的舍入18.5)= 11101101) 2 [算术右移的结果]
正如Guy Steele指出的那样 ,这种差异导致了多个编译器的bug 。 这里非负数(math)可以映射到无符号和有符号的非负值(C); 两者都是一样的,右移他们是通过整数除法完成的。
所以逻辑和算术在左移和右移的情况下是等价的。 负面价值观的转变正在发生变化。
操作数和结果types
标准C99§6.5.7 :
每个操作数应该有整数types。
整数升级在每个操作数上执行。 结果的types是升级的左操作数的types。 如果右操作数的值为负或大于或等于提升的左操作数的宽度,则行为是不确定的。
short E1 = 1, E2 = 3; int R = E1 << E2;
在上面的代码片断中,两个操作数变成了int
(由于整数提升)。 如果E2
为负值或者E2 ≥ sizeof(int) * CHAR_BIT
那么操作是不确定的。 这是因为移位超过可用位肯定会溢出。 如果R
被宣布为short
,则转换操作的int
结果将被隐含地转换为short
; 缩小的转换,如果该值不能在目标types中表示,则可能导致实现定义的行为。
左移
E1 << E2的结果是E1左移E2位的位置; 空位被零填充。 如果E1具有无符号types,则结果的值为E1×2 E2 ,比结果types中可表示的最大值减1。 如果E1具有带符号types和非负值,并且在结果types中E1×2 E2可表示,则结果值; 否则,行为是不确定的。
由于左移都是相同的,空位只填充零。 然后它表明,对于无符号和有符号types,这是一个算术转换。 我将它解释为算术移位,因为逻辑移位不会影响位表示的值,它只是把它视为一个位stream; 但标准不是用位来说话,而是用E1乘以2 E2得到的值来定义它。
这里需要注意的是对于有符号types,值应该是非负的,结果值应该可以在结果types中表示。 否则操作是不确定的。 结果types是应用积分提升后的E1types,而不是目的地(即将保存结果的variables)types。 结果值隐式转换为目标types; 如果在该types中不能表示,则转换是实现定义的(C99§6.3.1.3/ 3)。
如果E1是带负值的带符号types,则左移的行为是不确定的。 这是一个容易被忽略的未定义行为的简单途径。
右移
E1 >> E2的结果是E1右移E2位的位置。 如果E1具有无符号types,或者E1具有带符号types和非负值,则结果的值是E1 / 2 E2的商的整数部分。 如果E1有签名types和负值,则结果值是实现定义的。
对于无符号和有符号非负值的右移非常简单; 空位填充零。 对于有符号负值,右移的结果是实现定义的。 也就是说,像GCC和Visual C ++这样的大多数实现通过保留符号位来实现右移位作为算术移位。
结论
与Java有一个特殊的操作符>>>
用于逻辑移位和通常的>>
和<<
,C和C ++只有算术移位,有些区域没有定义和实现定义。 我认为它们是算术的原因是由于math上的操作标准措辞而不是将移位后的操作数视为位stream; 这也许就是为什么它将这些区域定义为实现定义的原因,而不是将所有的情况都定义为逻辑转换。
就你得到的转变types而言,重要的是你正在转移的价值的types。 错误的一个经典来源是当您将文字转换为掩蔽位时。 例如,如果你想放弃一个无符号整数的最左边的位,那么你可以试试这个作为你的掩码:
~0 >> 1
不幸的是,这会让你陷入麻烦,因为掩码将会设置所有的位,因为被移位的值(〜0)是有符号的,所以执行算术移位。 相反,你会想强制一个逻辑转移,通过显式声明该值为无符号的,即通过做这样的事情:
~0U >> 1;
这里有保证C中int整数的逻辑右移和算术右移的函数:
int logicalRightShift(int x, int n) { return (unsigned)x >> n; } int arithmeticRightShift(int x, int n) { if (x < 0 && n > 0) return x >> n | ~(~0U >> n); else return x >> n; }
当你这样做 – 左移1乘以2 – 右移1乘以2
x = 5 x >> 1 x = 2 ( x=5/2) x = 5 x << 1 x = 10 (x=5*2)
好吧,我在维基百科上查了一下 ,他们这样说:
但是,C只有一个右移操作符>>。 许多C编译器根据正在被移位的整数types来select执行哪个右移; 通常使用算术移位来移位有符号整数,并且使用逻辑移位来移位无符号整数。
所以这听起来像取决于你的编译器。 同样在那篇文章中,请注意,算术和逻辑的左移是相同的。 我会build议做一些简单的testing,在边界情况下使用一些有符号和无符号数字(当然是高位设置),看看你的编译器的结果是什么。 我也build议避免根据它是一个或另一个,因为它似乎C没有标准,至less如果这是合理的和可能的,以避免这种依赖。
左移<<
这在某种程度上很简单,只要你使用shift操作符,它总是一个按位操作,所以我们不能用double和float操作。 每当我们左移一个零,它总是被添加到最低有效位( LSB
)。
但在右移>>
我们必须遵循一个额外的规则,该规则被称为“符号位复制”。 “符号位复制”的含义是如果最高有效位( MSB
)被设置,则在右移一次之后MSB
将被置位,如果它被复位,则它被再次复位,意味着如果先前的值为零,则再次移位之后如果前一位是1,那么该位是零,然后在该位移之后它又是一位。 此规则不适用于左移。
右移最重要的一个例子是,如果你将任何负数移到右移,那么经过一些移位后,最后这个值达到零,然后在这之后,如果将这个-1移动任意次数,值将保持相同。 请检查。
gcc通常使用无符号variables的逻辑移位和有符号variables的左移。 算术右移是真正重要的,因为它将签署扩展variables。
gcc会在适用的时候使用它,就像其他编译器可能做的那样。
根据许多c编译器:
-
<<
是算术左移或左移。 -
>>
是一个算术右移或右移。