意外的C / C ++按位移位运算符结果
我想我会疯了。
我有一段代码需要创建一个(无符号)整数,其中N
后续位设置为1.确切地说,我有一个位掩码,在某些情况下,我想将它设置为一个实心的rnage。
我有以下function:
void MaskAddRange(UINT& mask, UINT first, UINT count) { mask |= ((1 << count) - 1) << first; }
简单来说: 1 << count
二进制表示中的1 << count
是100...000
(零的count
是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
(所有位都设置)。
然而,事实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 << 32
与1 << 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; }