C中的移位算子(<<,>>)是算术还是逻辑?

phic 发布于 2018-03-17 binary 最后更新 2018-03-17 01:00 815 浏览

在C中,移位运算符(<<>>)是算术还是逻辑?

已邀请:

raut

赞同来自:

当向左移动时,算术和逻辑移位之间没有区别。右移时,移位类型取决于正在移位的值的类型。 (作为不熟悉差异的读者的背景,“逻辑”右移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);

miure

赞同来自:

根据K&R 2nd edition,结果对于有符号值的右移是依赖于实现的。 Wikipedia表示C/C++'通常'对签名值执行算术移位。 基本上你需要测试你的编译器或不依赖它。我对当前MS C++编译器的VS2008帮助表示他们的编译器做了算术转换。

ddolor

赞同来自:

好吧,我看了it up on wikipedia,他们有这样的说法:

C, however, has only one right shift operator, >>. Many C compilers choose which right shift to perform depending on what type of integer is being shifted; often signed integers are shifted using the arithmetic shift, and unsigned integers are shifted using the logical shift.
所以这听起来像取决于你的编译器。同样在那篇文章中,请注意左移对于算术和逻辑是相同的。我建议用边界案例中的一些有符号和无符号数进行一个简单的测试(当然是高位集),并查看编译器的结果。我还建议避免依靠它是一个或另一个,因为它似乎C没有标准,至少如果是合理的并且可能避免这种依赖。

lvero

赞同来自:

就您所获得的转变类型而言,重要的是您正在转移的价值类型。一个典型的错误来源是当你将一个文字转换成掩码时。例如,如果你想放弃一个无符号整数的最左边的位,那么你可以试试这个作为你的掩码:

~0 >> 1
不幸的是,这会让你陷入困境,因为掩码将设置所有的位,因为被移位的值(~0)被标记,因此执行算术移位。相反,你需要通过显式声明该值为无符号来强制逻辑转换,即通过执行如下操作:
~0U >> 1;

tquod

赞同来自:

通常使用无符号变量的逻辑移位和有符号变量的左移。算术右移是真正重要的因素,因为它会签署扩展变量。 将在适用时使用此功能,因为其他编译器可能会这样做。

et_id

赞同来自:

以下是保证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;
}

ksit

赞同来自:

当你这样做   - 左移1乘以2   - 右移1除以2

 x = 5
 x >> 1
 x = 2 ( x=5/2)
x = 5
 x << 1
 x = 10 (x=5*2)

uautem

赞同来自:

根据许多编译器:

  1. <<是算术左移或左移位移。
  2. >>是一种算术右移或右移。

qui_id

赞同来自:

首先是来自数学观点的逻辑和算术移位之间的差异,而不用担心数据类型的大小。逻辑移位总是用零填充丢弃位,而算术移位用零填充仅用于左移,但是对于右移它复制MSB,从而保留操作数的符号(假定two's complement编码为负值)。 换句话说,逻辑移位将移位后的操作数视为位流并移动它们,而不会打扰生成值的符号。算术移位将它看作一个(带符号)数字,并在移位完成时保留符号。 数X乘n的左算术移位相当于将X乘以2 n,因此相当于逻辑左移;一个逻辑转换也会给出相同的结果,因为MSB无论如何都落在了最后,并且没有什么可以保留。 如果X是非负的,则X乘以n的正算术移位等于X的整数除以2 n!整数除只是数学划分,并且round为0(trunc)。 对于由二进制补码编码表示的负数,右移n位具有数学上将其除以2 n和/或向-∞(floor)取整的效果;因此对于非负值和负值,右移是不同的。
for X ≥ 0, X >> n = X/2n = trunc(X ÷ 2n) for X < 0, X >> n = floor(X ÷ 2n)
÷是数学除法,/是整数除法。我们来看一个例子:
37)10 = 100101)2 37 ÷ 2 = 18.5 37/2 = 18 (rounding 18.5 towards 0) = 10010)2 [result of arithmetic right shift] -37)10 = 11011011)2 (considering a two's complement, 8-bit representation) -37 ÷ 2 = -18.5 -37/2 = -18 (rounding 18.5 towards 0) = 11101110)2 [NOT the result of arithmetic right shift] -37 >> 1 = -19 (rounding 18.5 towards −∞) = 11101101)2 [result of arithmetic right shift]
由于Guy Steele pointed out,这种差异导致了bugs in more than one compiler。这里非负数(数学)可以映射到无符号和有符号的非负值(C);两者都是相同的,右移是通过整数除法完成的。 所以逻辑和算术在左移和右移中的非负值是等价的;它们正在转变为负值,它们有所不同。

操作数和结果类型 标准C99§6.5.7
Each of the operands shall have integer types. The integer promotions are performed on each of the operands. The type of the result is that of the promoted left operand. If the value of the right operand is negative or is greater than or equal to the width of the promoted left operand, the behaviour is undefined.
short E1 = 1, E2 = 3;
int R = E1 << E2;
在上面的代码片段中,两个操作数变成int(由于整数提升);如果E2为负值或E2 ≥ sizeof(int) * CHAR_BIT则操作未定义。这是因为移位超过可用位肯定会溢出。如果R被声明为short,则移位操作的int结果将隐式转换为short;缩小的转换,如果该值在目标类型中不可表示,则可能导致实现定义的行为。

左移
The result of E1 << E2 is E1 left-shifted E2 bit positions; vacated bits are filled with zeros. If E1 has an unsigned type, the value of the result is E1×2E2, reduced modulo one more than the maximum value representable in the result type. If E1 has a signed type and non-negative value, and E1×2E2 is representable in the result type, then that is the resulting value; otherwise, the behaviour is undefined.
由于左移与左移相同,空位仅填充零。然后它表明,对于无符号和有符号类型,这是一个算术转换。我将它解释为算术移位,因为逻辑移位不会影响位表示的值,它只是将它视为一个位流;但是标准不是以比特为单位进行讨论,而是通过用E1与2 E2的乘积得到的值来定义它。 这里需要注意的是,对于有符号类型,值应该是非负值,结果值应该可以在结果类型中表示。否则,操作未定义。结果类型是应用积分提升后的E1的类型,而不是目的地(将要保存结果的变量)类型。结果值隐式转换为目标类型;如果该类型不能表示,则该转换是实现定义的(C99§6.3.1.3/ 3)。 如果E1是带负值的带符号类型,那么左移的行为是未定义的。这是一种容易被忽略的未定义行为。

右移
The result of E1 >> E2 is E1 right-shifted E2 bit positions. If E1 has an unsigned type or if E1 has a signed type and a non-negative value, the value of the result is the integral part of the quotient of E1/2E2. If E1 has a signed type and a negative value, the resulting value is implementation-defined.
对于无符号和带符号的非负值的右移非常简单;空位填充零。 对于带符号的负值,右移的结果是实现定义的。也就是说,像GCC和Visual C++这样的大多数实现通过保留符号位来实现右移位作为算术移位。

结论 与Java不同,Java除了通常的>><<之外,还有一个特殊的>>>操作符,用于逻辑移位,C和C++只进行算术移位,其中一些区域未定义并且实现定义。我认为它们是算术的原因是由于数学上的操作标准措辞而不是将移位后的操作数视为位流;这也许就是为什么它将这些领域定义为未实现定义的原因,而不是将所有情况定义为逻辑转换。

set

赞同来自:

左移<< 这在某种程度上很简单,只要你使用移位运算符,它总是一个按位运算,所以我们不能在double和float运算中使用它。每当我们左移一个零时,它总是被添加到最低有效位(LSB)。 但是在右移>>中,我们必须遵循一个附加规则,该规则称为“符号位复制”。 “符号位复制”的意义在于如果最高有效位(MSB)被设置,则在右移再次之后,如果MSB被复位,则其将被设置,然后再次被复位,意味着如果先前值为零,则再次移位之后如果前一位是1,那么该位为零,然后在该位移之后它又是1。此规则不适用于左移。 右移最重要的例子是,如果您将任何负数移到右移,那么在经过一些移位后,该值最终达到零,然后在此之后如果将此-1移位任意次数,值将保持相同。请检查。