意外的C / C ++按位移位运算符结果

我想我会疯了。

我有一段代码需要创建一个(无符号)整数,其中N后续位设置为1.确切地说,我有一个位掩码,在某些情况下,我想将它设置为一个实心的rnage。

我有以下function:

 void MaskAddRange(UINT& mask, UINT first, UINT count) { mask |= ((1 << count) - 1) << first; } 

简单来说: 1 << count二进制表示中的1 << count100...000 (零的countcount ),从这样的数字中减去1得到011...111 ,然后我们first它左移。

当满足以下明显限制时,上述结果应产生正确的结果:

first + count <= sizeof(UINT)*8 = 32

请注意 ,它也应该适用于“极端”情况。

  • 如果count = 0我们有(1 << count) = 1 ,因此((1 << count) - 1) = 0
  • 如果count = 32我们有(1 << count) = 0 ,因为前导位溢出,并且根据C / C ++规则,按位移位运算符不是循环的 。 然后((1 << count) - 1) = -1 (所有位都设置)。

然而,事实certificate,对于count = 32 ,公式不能按预期工作。 如发现:

 UINT n = 32; UINT x = 1 << n; // the value of x is 1 

而且,我正在使用MSVC2005 IDE。 当我在调试器中评估上面的表达式时,结果为0.但是,当我跳过上面的行时, x得到值1.通过反汇编程序,我们看到以下内容:

 mov eax,1 mov ecx,dword ptr [ebp-0Ch] // ecx = n shl eax,cl // eax <<= LOBYTE(ecx) mov dword ptr [ebp-18h],eax // n = ecx 

确实没有魔法,编译器只使用了shl指令。 然后似乎shl没有做我预期应该做的事情。 CPU决定忽略该指令,或者将移位视为模32,或者不知道什么。

我的问题是:

  • shl / shr指令的正确行为是什么?
  • 是否有CPU标志控制位移指令?
  • 这是根据C / C ++标准吗?

提前致谢

编辑:

谢谢你的回答。 我已经意识到(1) shl / shr确实处理操作数模32(或&0x1F)和(2)C / C ++标准将移位超过31位视为未定义的行为。

然后我还有一个问题。 我怎样才能重写我的“掩蔽”表达来覆盖这种极端情况。 它应该没有分支( if? )。 什么是最简单的表达?

1U << 32是C和C ++中未定义的行为,当unsigned int类型为32位宽时。

(C11,6.5.7p3)“如果右操作数的值为负或大于或等于提升的左操作数的宽度,则行为未定义”

(C ++ 11,5.8p1)“如果右操作数为负数,或大于或等于提升左操作数的位长度,则行为未定义。”

在C和C ++中, 移动的位数与在移位的整数类型中的位数相同。 在x86和x86_64上,移位指令的移位量确实以模32(或操作数大小为单位)处理。 但是, 您不能依赖编译器从C或C ++ >> / <<操作生成此模数行为,除非您的编译器在其文档中明确保证它。

我认为表达式1 << 321 << 0相同。 IA-32指令集参考表示移位指令的计数操作数被屏蔽为5位。

可以在此处找到IA-32架构的指令集参考。

为了修复“极端”的情况,我只能提出以下可能有点尴尬的代码(也许是错误的):

 void MaskAddRange(UINT *mask, UINT first, UINT count) { int count2 = ((count & 0x20) >> 5); int count1 = count - count2; *mask |= (((1 << count1) << count2) - 1) << first; } 

基本思路是拆分移位操作,使每个移位计数不超过31.显然,上面的代码假设计数在0..32的范围内,所以它不是很健壮。

如果我已经理解了这些要求,你需要一个unsigned int,并设置前N位吗?

有几种方法可以得到你想要的结果(我想)。 编辑:我担心这不是非常强大,并且对于n> 32将失败:

 uint32_t set_top_n(uint32 n) { static uint32_t value[33] = { ~0xFFFFFFFF, ~0x7FFFFFFF, ~0x3FFFFFFF, ~0x1FFFFFFF, ~0x0FFFFFFF, ~0x07FFFFFF, ~0x03FFFFFF, ~0x01FFFFFF, ~0x00FFFFFF, ~0x007FFFFF, ~0x003FFFFF, ~0x001FFFFF, // you get the idea 0xFFFFFFFF }; return value[n & 0x3f]; } 

这应该非常快,因为它只有132个字节的数据。

为了使其健壮,我要么将所有值扩展到63,要么使其成为条件,在这种情况下,可以使用原始位掩码的版本+ 32个案例。 即

我的32美分:

 #include  #define INT_BIT (CHAR_BIT * sizeof(int)) unsigned int set_bit_range(unsigned int n, int frm, int cnt) { return n | ((~0u >> (INT_BIT - cnt)) << frm); } 

清单1。

具有伪造/半圆形结果的安全版本可能是:

 unsigned int set_bit_range(unsigned int n, int f, int c) { return n | (~0u >> (c > INT_BIT ? 0 : INT_BIT - c)) << (f % INT_BIT); } 

清单2。

没有分支或局部变量这样做可能是这样的;

 return n | (~0u >> ((INT_BIT - c) % INT_BIT)) << (f % INT_BIT); 

清单3。

列表2列表3只要from INT_BIT和> = 0,这将给出“正确”的结果。即:

 ./bs 1761 26 810 Setting bits from 26 count 810 in 1761 -- of 32 bits Trying to set bits out of range, set bits from 26 to 836 in 32 sized range x = ~0u = 1111 1111 1111 1111 1111 1111 1111 1111 Unsafe version: x = x >> -778 = 0000 0000 0000 0000 0000 0011 1111 1111 x = x << 26 = 1111 1100 0000 0000 0000 0000 0000 0000 x v1 Result = 1111 1100 0000 0000 0000 0110 1110 0001 Original: 0000 0000 0000 0000 0000 0110 1110 0001 Safe version, branching: x = x >> 0 = 1111 1111 1111 1111 1111 1111 1111 1111 x = x << 26 = 1111 1100 0000 0000 0000 0000 0000 0000 x v2 Result = 1111 1100 0000 0000 0000 0110 1110 0001 Original: 0000 0000 0000 0000 0000 0110 1110 0001 Safe version, modulo: x = x >> 22 = 0000 0000 0000 0000 0000 0011 1111 1111 x = x << 26 = 1111 1100 0000 0000 0000 0000 0000 0000 x v3 Result = 1111 1100 0000 0000 0000 0110 1110 0001 Original: 0000 0000 0000 0000 0000 0110 1110 0001 

您可以通过分两步拆分移位操作来避免未定义的行为,第一步是(count – 1)位,第二位是1位。 如果计数为零,则需要特别小心,但是:

 void MaskAddRange(UINT& mask, UINT first, UINT count) { if (count == 0) return; mask |= ((1 << (count - 1) << 1) - 1) << first; }