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

有帮助吗?

解决方案

根据 K&R 第二版 对于有符号值的右移,结果取决于实现。

维基百科 说 C/C++“通常”对有符号值实现算术移位。

基本上,您需要测试您的编译器或不依赖它。我对当前 MS C++ 编译器的 VS2008 帮助说他们的编译器会进行算术移位。

其他提示

左移时,算术移位和逻辑移位没有区别。右移时,移位类型取决于被移位的值的类型。

(作为那些不熟悉差异的读者的背景知识,“逻辑”右移 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);

长话短说

考虑 in 分别为移位运算符的左操作数和右操作数;的类型 i, ,整数提升后,为 T. 。假设 n 将在 [0, sizeof(i) * CHAR_BIT) — 否则未定义 — 我们有这些情况:

| Direction  |   Type   | Value (i) | Result                   |
| ---------- | -------- | --------- | ------------------------ |
| Right (>>) | unsigned |    ≥ 0    | −∞ ← (i ÷ 2ⁿ)            |
| Right      | signed   |    ≥ 0    | −∞ ← (i ÷ 2ⁿ)            |
| Right      | signed   |    < 0    | Implementation-defined†  |
| Left  (<<) | unsigned |    ≥ 0    | (i * 2ⁿ) % (T_MAX + 1)   |
| Left       | signed   |    ≥ 0    | (i * 2ⁿ) ‡               |
| Left       | signed   |    < 0    | Undefined                |

† 大多数编译器将其实现为算术移位
‡ 如果值溢出结果类型 T,则未定义;i 的提升类型


变速

首先是从数学角度来看逻辑移位和算术移位之间的区别,而无需担心数据类型大小。逻辑移位总是用零填充丢弃的位,而算术移位仅在左移时用零填充,但对于右移,它会复制 MSB,从而保留操作数的符号(假设 二进制补码 负值的编码)。

换句话说,逻辑移位将移位的操作数视为位流并移动它们,而不关心结果值的符号。算术移位将其视为(有符号)数字,并在进行移位时保留符号。

数字 X 左移 n 相当于 X 乘以 2n 因此相当于逻辑左移;逻辑移位也会给出相同的结果,因为 MSB 无论如何都会从末尾掉下来并且没有什么可以保留的。

数字 X 的算术右移 n 相当于 X 除以 2n 仅当 X 为非负数时!整数除法只不过是数学除法 圆形的 向 0 (截断).

对于由二进制补码编码表示的负数,右移 n 位具有数学上将其除以 2 的效果n 并向−∞舍入(地面);因此,右移对于非负值和负值是不同的。

对于 X ≥ 0,X >> n = X / 2n = 截断(X ÷ 2n)

对于 X < 0,X >> n = 下限(X ÷ 2n)

在哪里 ÷ 是数学除法, / 是整数除法。让我们看一个例子:

37)10 = 100101)2

37 ÷ 2 = 18.5

37 / 2 = 18(将 18.5 向 0 舍入)= 10010)2 [算术右移结果]

-37)10 = 11011011)2 (考虑二进制补码,8 位表示)

-37 ÷ 2 = -18.5

-37 / 2 = -18(将 18.5 向 0 舍入)= 11101110)2 [不是算术右移的结果]

-37 >> 1 = -19(将 18.5 四舍五入至 −∞)= 11101101)2 [算术右移结果]

作为 盖伊·斯蒂尔指出, ,这种差异导致了 多个编译器中的错误. 。这里非负(数学)可以映射到无符号和有符号非负值(C);两者的处理方式相同,并且通过整数除法来右移它们。

因此逻辑和算术在左移中是等效的,而对于非负值在右移中是等效的;它们的不同之处在于负值的右移。

操作数和结果类型

标准 C99 §6.5.7:

每个操作数都应具有整数类型。

对每个操作数执行整数提升。结果的类型是提升后的左操作数的类型。如果右操作数的值为负数或大于或等于提升的左操作数的宽度,则行为未定义。

short E1 = 1, E2 = 3;
int R = E1 << E2;

在上面的代码片段中,两个操作数都变成 int (由于整数提升);如果 E2 为负值或 E2 ≥ sizeof(int) * CHAR_BIT 那么该操作是未定义的。这是因为移位超过可用位肯定会溢出。有 R 被宣布为 short, , 这 int 移位运算的结果将隐式转换为 short;缩小转换,如果该值无法在目标类型中表示,则可能会导致实现定义的行为。

左移

E1 << E2 的结果是 E1 左移 E2 位位置;空出的位用零填充。如果E1为无符号类型,则结果值为E1×2E2, ,比结果类型中可表示的最大值减少模一。如果E1有符号类型且非负值,并且E1×2E2 可以用结果类型表示,那么这就是结果值;否则,行为是未定义的。

由于两者的左移相同,因此空出的位只需用零填充。然后它指出对于无符号和有符号类型来说,它都是算术移位。我将其解释为算术移位,因为逻辑移位并不关心位表示的值,它只是将其视为位流;但该标准不是用比特来讨论的,而是用 E1 与 2 的乘积获得的值来定义它E2.

这里需要注意的是,对于有符号类型,该值应该是非负的,并且结果值应该可以在结果类型中表示。否则操作未定义。 结果类型将是应用整数提升后 E1 的类型,而不是目标(将保存结果的变量)类型。结果值隐式转换为目标类型;如果它不能用该类型表示,则转换是实现定义的(C99 §6.3.1.3/3)。

如果 E1 是具有负值的有符号类型,则左移的行为未定义。 这是导致未定义行为的简单途径,很容易被忽视。

右移

E1 >> E2 的结果是 E1 右移 E2 位位置。如果 E1 为无符号类型或 E1 为有符号类型且非负值,则结果的值为 E1/2 商的整数部分E2. 。如果 E1 具有有符号类型和负值,则结果值是实现定义的。

无符号和有符号非负值的右移非常简单;空位用零填充。 对于带符号的负值,右移的结果是实现定义的。 也就是说,大多数实现如 GCC 和 视觉C++ 通过保留符号位将右移实现为算术移位。

结论

与Java不同,Java有一个特殊的运算符 >>> 用于与通常不同的逻辑转移 >><<, 、C 和 C++ 仅具有算术移位,某些区域未定义且由实现定义。我将它们视为算术的原因是由于数学运算的标准措辞,而不是将移位的操作数视为位流;这也许就是为什么它没有/实现定义这些区域,而不是仅仅将所有情况定义为逻辑转变的原因。

就您获得的转变类型而言,重要的是您要转变的值的类型。错误的一个典型来源是当您将文字转换为屏蔽位时。例如,如果您想删除无符号整数的最左边位,那么您可以尝试将其作为掩码:

~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编译器选择要执行的正确移动,具体取决于正在移动哪种类型的整数。通常,使用算术偏移将签名的整数转移,并使用逻辑移位来移动无符号整数。

所以听起来这取决于你的编译器。另外,在那篇文章中,请注意左移对于算术和逻辑是相同的。我建议在边界情况下使用一些带符号和无符号的数字(当然是高位集)进行简单的测试,然后看看编译器上的结果是什么。我还建议避免依赖其中之一,因为 C 似乎没有标准,至少在合理且可能避免这种依赖的情况下。

左移 <<

这在某种程度上很简单,每当您使用移位运算符时,它总是按位运算,因此我们不能将它与双精度和浮点运算一起使用。每当我们左移一个零时,它总是被添加到最低有效位(LSB).

但右移时 >> 我们必须遵循一项附加规则,该规则称为“符号位复制”。“符号位复制”的含义是如果最高有效位(MSB)然后在右移后再次设置 MSB 如果被重置,则将被设置,然后再次重置,意味着如果先前的值为零,则在再次移位后,该位为零,如果先前的位为 1,则在移位后,该位再次为 1。此规则不适用于左移。

关于右移的最重要的例子,如果将任何负数右移,那么经过一些移位后,该值最终达到零,然后如果将这个 -1 移动任意次数,该值将保持不变。请检查。

通常对无符号变量使用逻辑移位,对有符号变量使用左移。算术右移是真正重要的一项,因为它会对变量进行符号扩展。

will 将在适用时使用它,就像其他编译器可能会做的那样。

海湾合作委员会确实

  1. for -ve -> 算术移位

  2. 对于 +ve -> 逻辑移位

据许多人称 编译器:

  1. << 是算术左移或按位左移。
  2. >> 是算术右移或按位右移。
许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top