移位可以在 C 中添加 1 而不是零吗?以及如何避免呢?

Can bit shifting ever add a 1 instead of a zero in C ? And how to avoid it?

我有一个程序,它使用第 0 位来处理某些事情,然后使用第 7-1 位来处理其他事情。为了检查第 7-1 位,我做了

int number = number >> 1;

这给了我位 7-1。

所以例如 1111 1110 变成 0111 1111

我很好奇是否存在左位将变为 1 而不是 0 的边缘情况?因为那会搞砸我的程序。

如果有这种情况,我该如何避免它并确保它永远不会发生?

右移后,空格由前导0填充

例如, 11111000 >> 1 = 01111100

如果它是无符号数据类型,它将始终以 0 开头,除非使用移位运算符明确设为 1。

提示 - 您可以确保您的程序使用无符号数据类型始终获得前导零。您将变量声明为任何无符号类型。例子 - unsigned int、unsigned char、unsigned long 等

有很多事情要考虑。

首先如果它是 int - 并且它是 unsigned 你可以确定所有左移的位都是 0-'s。第 8 位应始终为 0,以确保 8th 位为 0,因为这就是左移后的 7th 位。

对于签名号码,情况就完全不同了。假设这是 int8_t 并且右移这是实现定义的。但在大多数实现中它会给 1110 0000 右移后 1010 0000。就是这样。

现在你说可以char。三种 char - signed , unsignedplain.

unsigned 的故事和以前一样。签名后也和以前一样。使用 plain 你不知道 char 默认情况下是如何解释你的实现的。如何查看它是什么?

检查 CHAR_MIN 以确定它是 signed 还是 unsigned 然后如果它是 signed 然后右移的结果是实现定义的标准。所以在这种情况下,它也包含关于签名类型的信息。

C11 §6.5.7 Shift operators ¶5 说:如果 E1 具有 signed 类型和负值,则 结果值为 implementation-defined.(乔纳森·莱弗勒指出了这一点)

这取决于号码的类型:

  • 如果它是无符号的,C 标准定义您将移入 0 位。所以,这很好。

  • 如果它是有符号的,你通常会得到不同的移位操作:移入的位是符号位的副本。也就是说,

    uint8_t result = (int8_t)128 >> 1;
    

    将产生 result == 192 或二进制形式的 11000000

确保正确操作的最简单方法是使用适当的整数类型。在你的情况下,那将是 uint8_t.

使用

char 是一个非常糟糕的主意,因为实现定义了 char 是否被视为有符号或无符号。因此,除非您检查了编译器文档,否则您不知道 (char)128 >> 1 的结果是什么。所以最好避免使用 char.

I am curious if there is an edge case where that left bit will become a 1 instead of a 0?

是 - 首先,这不是在 initialized/assigned.

之前尝试使用 number 的指定行为
//           v----v----- number should not be used yet
int number = number >> 1;

让我们将其更改为

int number = foo();
number = number >> 1;

是的。 Post 没有指定位 8、9、10 等的值。下面可以轻松地将 1 位移入 "left" 位 7。

number = 0x100;
number = number >> 1;

可能是隐含的,但没有指定高位的值为0。这就是缺乏特异性的风险。您可能了解目标,但下一个开发人员可能不了解 - 而且永远不会很长一段时间。


how can I avoid it and make sure it never happens?

从不 发生很容易 - 确保用掩码清除高位 - 如果不需要,让编译器优化掩码。如果代码使用 16 个或更多位而不是 8 个位 1,则使用 unsigned 数学会有所帮助。即使 numberintunsigned 或任何整数类型,此功能也能正常工作。

number = (number & 0xFF) >> 1;

1 对于 16 位或更多位,其中一位可能是带符号的位,最好避免对其进行移位。如果 number 是一个 有符号整数 编码为 有符号大小 补数 .