有符号整数上的算术位移

new*_*int 53 c bit-manipulation

我试图弄清楚算术位移运算符在C中是如何工作的,以及它将如何影响带符号的32位整数.

为简单起见,假设我们在一个字节(8位)内工作:

x = 1101.0101
MSB[ 1101.0101 ]LSB
Run Code Online (Sandbox Code Playgroud)

在Stack Overflow和一些网站上阅读其他帖子,我发现: <<将转向MSB(在我的情况下向左),并用0填充"空"LSB位.

>>会向着转向LSB(向右,在我的情况),并填写"空"与MS位位

因此,x = x << 7将导致LSB移动到MSB,并将所有内容设置为0.

1000.0000
Run Code Online (Sandbox Code Playgroud)

现在,让我说我会>> 7,最后的结果.这会导致[0000.0010]?我对吗?

关于转移运营商我的假设是对的吗?

我刚在我的机器上测试过,**

int x = 1;   //000000000......01

x = x << 31; //100000000......00

x = x >> 31; //111111111......11 (Everything is filled with 1s !!!!!) 
Run Code Online (Sandbox Code Playgroud)

为什么?

Mat*_*ery 59

负签名号的右移具有实现定义的行为.

如果您的8位用于表示带符号的8位值(正如您在切换到8位示例之前所说的那样是"带符号的32位整数"),那么您的数字为负数.向右移位可以用原始MSB填充"空"位(即执行符号扩展),或者它可以在零中移位,这取决于平台和/或编译器.

(实现定义的行为意味着编译器会做一些合理的事情,但是以平台相关的方式;编译器文档应该告诉你什么.)


左移,如果数字开始为负,或者移位操作将1移位到符号位或超出符号位,则具有未定义的行为(对于导致溢出的有符号值的大多数操作也是如此).

(未定义的行为意味着任何事情都可能发生.)


在两种情况下,对无符号值的相同操作都是明确定义的:"空"位将用0填充.

  • 应该说,具有标准、可移植语义的右移也称为除以二的幂的文字。大多数编译器会为这种除法生成算术移位,从而生成根据需要进行符号扩展的快速代码。 (2认同)
  • @KubaOber:现代标准要求"x/2"在"x"为负奇数的情况下与右移不同.正常的算术右移将执行浮动除法(因此在不溢出`(n + d)/ d ==(n/d)+ 1`的情况下,但是需要除法使用截断除法,从而防止编译器使用简单的班次. (2认同)
  • 最好指出在超现代编译器上,“任何事情”都包括重新排列程序逻辑以假装负值是正值。给定 `void foo(int x) { if (x &gt;= 0) printf("Kaboom!"); int y= x&lt;&lt;4; }` 调用 `foo(-1);` 很可能会打印“Kaboom!”。 (2认同)

pmg*_*pmg 35

没有为负值定义按位移位操作

为'<<'

6.5.7/4 [...]如果E1有一个签名的类型和非负的值,E1×2 E2是在结果类型表示的,那么这是所得到的值; 否则,行为未定义.

和'>>'

6.5.7/5 [...]如果E1具有有符号类型和负值,则结果值是实现定义的.

这是一个时间来研究这些操作的行为上有符号数在具体实施一种浪费,因为你不能保证它完全可以在其他任何执行同样的方式(实现的,例如,您编译您的计算机上你特定的commad-line参数).

它甚至可能不适用于相同编译器的较旧版本或较新版本.编译器甚至可能将这些位定义为随机或未定义.这意味着在源代码中使用完全相同的代码序列可能会产生完全不同的结果,甚至取决于汇编优化或其他寄存器使用等事情.如果封装在一个函数中,它可能甚至不会在具有相同参数的两个连续调用中的那些位中产生相同的结果.

仅考虑非负值,左移1(expression << 1)的效果与表达式乘法2相同(假设表达式*2不溢出),右移1(expression >> 1)的效果与除以2.


exa*_*ple 8

从 c++20 开始,有符号整数的按位移位运算符定义良好。

左移a<<b等效于a*2^b模数2^N,其中N是结果类型中的位数。尤其1<<31是实际上是最小值int

右移a>>b相当于a/2^b,向下取整(即朝负无穷大)。所以例如-1>>10 == -1

有关更多详细信息,请参阅https://en.cppreference.com/w/cpp/language/operator_arithmetic

(对于较旧的标准,请参阅 Matthew Slattery 的答案)


Vov*_*ium 5

正如其他人所说,负值的转移是实施定义的.

大多数实现通过使用符号位填充移位来将带符号的右移作为floor(x/2 N)处理.这在实践中非常方便,因为这种操作非常普遍.另一方面,如果您将右移无符号整数,则位移位将归零.

从机器方面来看,大多数实现都有两种类型的右移指令:

  1. 一个'算术'右移(通常有助记符ASR或SRA),就像我解释的那样.

  2. "逻辑"右移(具有助记符LSR或SRL或SR)可以按预期工作.

大多数编译器首先用于签名类型,第二个用于无符号类型.只是为了方便.