所以我在C语言中乱搞Bit-Twiddling,我遇到了一个有趣的输出:
int main()
{
int a = 0x00FF00FF;
int b = 0xFFFF0000;
int res = (~b & a);
printf("%.8X\n", (res << 8) | (b >> 24));
}
此声明的输出是:
FFFFFFFF
我预计输出为
0000FFFF
但为什么不呢?我在这里错过了一些比特移位的东西吗?
答案 0 :(得分:5)
TLDR:你的整数b是负数,所以当你向右移动时,最高位(即1)的值保持不变。因此,当你将b向右移动24个位置时,你最终会得到0xFFFFFFFF。
更长的解释:
假设您的平台上的整数为32位或更长,并且有符号整数由2的补码表示,则分配给有符号整数变量的0xFFFF0000为负数。如果int超过32位,则0xFFFF0000将首先进行符号扩展,但仍为负数。
向右移动负数是标准所定义的实施(C99 / N1256,第6.5.7.5节):
E1&gt;的结果&gt; E2是E1右移E2位位置。 [...]如果E1 有一个有符号的类型和一个负值,结果值是 实施定义。
这意味着特定的编译器可以选择在特定情况下发生的情况,但应在编译器手册中注明效果是什么。
在许多处理器中往往有两组移位指令,即逻辑移位和算术移位。逻辑右移将移位,并用零填充暴露的位。算术右移(假设再次为2的补码)将使用最高有效位的相同位值填充暴露的位,以使其结果与使用移位除以2的结果一致。(例如,-4 &gt;&gt; 1 == 0xFFFFFFFC&gt;&gt; 1 == 0xFFFFFFFE == -2。)
在您的情况下,似乎编译器实现者在应用于有符号整数时选择使用算术移位,因此将负值向右移动的结果仍为负值。在位模式方面0xFFFF0000&gt;&gt; 24给出0xFFFFFFFF。
除非你完全确定你在做什么,否则最好只对无符号类型执行按位运算,因为它们的内部表示可以安全地被视为一组位。您可能还希望确保在这种情况下使用的任何数值都是无符号的,方法是将无符号后缀附加到您的数字上。
答案 1 :(得分:2)
右移负值(如b
)可以用两种不同的方式定义:逻辑移位,用左边的零填充值(当产生正数时产生正数)移动非零量)和算术移位,它将值填充为1(总是产生负数)。 C中使用的定义是实现定义的,您的编译器显然使用算术移位,因此b >> 24
为0xFFFFFFFF
。
答案 2 :(得分:1)
b&gt;&gt; 24给出0xFFFFFFFF 带负数的右键垫
List = (res << 8) | (b >> 24)
a = 0x00FF00FF = 0000 0000 1111 1111 0000 0000 1111 1111
b = 0xFFFF0000 = 1111 1111 1111 1111 0000 0000 0000 0000
~b = 0x0000FFFF = 0000 0000 0000 0000 1111 1111 1111 1111
~b & a = 0x000000FF = 0000 0000 0000 0000 0000 0000 1111 1111, = res
res << 8 = 0x0000FF00 = 0000 0000 0000 0000 1111 1111 0000 0000
b >> 24 = 0xFFFFFFFF = 1111 1111 1111 1111 1111 1111 1111 1111
List = 0xFFFFFFFF = 1111 1111 1111 1111 1111 1111 1111 1111
答案 3 :(得分:0)
黄金法则:永远不要将带符号的运算符与有符号运算符混合。
将所有整数更改为无符号整数。正如预防措施一样,将所有文字也改为无符号。
#include <stdint.h>
uint32_t a = 0x00FF00FFu;
uint32_t b = 0xFFFF0000u;
uint32_t res = (~b & a);