移位可以在 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
, unsigned
和 plain
.
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
数学会有所帮助。即使 number
是 int
或 unsigned
或任何整数类型,此功能也能正常工作。
number = (number & 0xFF) >> 1;
1 对于 16 位或更多位,其中一位可能是带符号的位,最好避免对其进行移位。如果 number
是一个 有符号整数 编码为 有符号大小 或 补数 .
我有一个程序,它使用第 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
, unsigned
和 plain
.
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
数学会有所帮助。即使 number
是 int
或 unsigned
或任何整数类型,此功能也能正常工作。
number = (number & 0xFF) >> 1;
1 对于 16 位或更多位,其中一位可能是带符号的位,最好避免对其进行移位。如果 number
是一个 有符号整数 编码为 有符号大小 或 补数 .