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

时间:2012-03-25 13:35:14

标签: c bit-manipulation

我想我会因此而疯狂。

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

我有以下功能:

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

简单来说:二进制表示中的1 << count100...000(零的数量为count),从这样的数字中减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(设置所有位)。

然而,事实证明,对于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说明的正确行为是什么?
  • 是否存在控制bitshift指令的CPU标志?
  • 这是根据C / C ++标准吗?

提前致谢

修改

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

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

6 个答案:

答案 0 :(得分:11)

当类型1U << 32为32位宽时,

unsigned int在C和C ++中是未定义的行为。

  

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

     

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

答案 1 :(得分:4)

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

答案 2 :(得分:3)

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

可以找到IA-32架构的指令集参考here

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

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的范围内,所以它不是很健壮。

答案 3 :(得分:1)

如果我已经理解了这些要求,你需要一个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个案例。即。

答案 4 :(得分:1)

我的32美分:

#include <limits.h>

#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且&gt; = 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

答案 5 :(得分:0)

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

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