我想我会因此而疯狂。
我有一段代码需要创建一个(无符号)整数,其中N
后续位设置为1.确切地说,我有一个位掩码,在某些情况下,我想将其设置为坚实的。
我有以下功能:
void MaskAddRange(UINT& mask, UINT first, UINT count)
{
mask |= ((1 << count) - 1) << first;
}
简单来说:二进制表示中的1 << count
为100...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
说明的正确行为是什么?提前致谢
修改
感谢您的回答。我已经意识到(1)shl
/ shr
确实将操作数模32(或&amp; 0x1F)和(2)C / C ++标准视为未定义行为的移位超过31位。 / p>
然后我还有一个问题。我怎样才能重写我的“掩蔽”表达来覆盖这种极端情况。它应该没有分支(if
,?
)。什么是最简单的表达?
答案 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 << 32
与1 << 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;
}