Kum*_*mba 14 c bit-manipulation
我有点好奇创建一个宏来为设备寄存器生成位掩码,最高可达64位.这样就BIT_MASK(31)产生了0xffffffff.
但是,有几个C示例不能像我想的那样工作,而是我得到的0x7fffffff.这是因为编译器假设我想要签名输出,而不是无符号输出.所以我尝试了32,并注意到该值回绕到0.这是因为C标准声明如果移位值大于或等于要移位的操作数中的位数,则结果是未定义的.那讲得通.
但是,鉴于以下计划,bits2.c:
#include <stdio.h>
#define BIT_MASK(foo) ((unsigned int)(1 << foo) - 1)
int main()
{
unsigned int foo;
char *s = "32";
foo = atoi(s);
printf("%d %.8x\n", foo, BIT_MASK(foo));
foo = 32;
printf("%d %.8x\n", foo, BIT_MASK(foo));
return (0);
}
Run Code Online (Sandbox Code Playgroud)
如果我编译gcc -O2 bits2.c -o bits2,并在Linux/x86_64机器上运行它,我得到以下内容:
32 00000000
32 ffffffff
Run Code Online (Sandbox Code Playgroud)
如果我使用相同的代码并在Linux/MIPS(big-endian)机器上编译它,我得到这个:
32 00000000
32 00000000
Run Code Online (Sandbox Code Playgroud)
在x86_64机器上,如果我使用gcc -O0 bits2.c -o bits2,那么我得到:
32 00000000
32 00000000
Run Code Online (Sandbox Code Playgroud)
如果我调整BIT_MASK到((unsigned int)(1UL << foo) - 1),那么32 00000000无论gcc的优化级别如何,输出都适用于两种形式.
因此,似乎在x86_64上,gcc正在优化某些内容,或者32位数字上左移32位的未定义特性由每个平台的硬件决定.
考虑到上述所有情况,是否可以以编程方式创建一个C宏,从单个位或位范围创建位掩码?
即:
BIT_MASK(6) = 0x40
BIT_FIELD_MASK(8, 12) = 0x1f00
Run Code Online (Sandbox Code Playgroud)
假设BIT_MASK并BIT_FIELD_MASK从0指数(0-31)开始操作. BIT_FIELD_MASK是从位范围创建一个掩码,即8:12.
Die*_*Epp 12
这是宏的一个版本,可用于任意正输入.(负输入仍然会调用未定义的行为......)
#include <limits.h>
/* A mask with x least-significant bits set, possibly 0 or >=32 */
#define BIT_MASK(x) \
(((x) >= sizeof(unsigned) * CHAR_BIT) ?
(unsigned) -1 : (1U << (x)) - 1)
Run Code Online (Sandbox Code Playgroud)
当然,这是一个有点危险的宏,因为它两次评估它的论点.static inline如果您使用GCC或目标C99,这是一个很好的机会.
static inline unsigned bit_mask(int x)
{
return (x >= sizeof(unsigned) * CHAR_BIT) ?
(unsigned) -1 : (1U << x) - 1;
}
Run Code Online (Sandbox Code Playgroud)
正如Mysticial所指出的那样,使用32位整数移位超过32位会导致实现定义的未定义行为.以下是三种不同的转换实现:
x << 32 == x.x << 32 == 0但是x << 64 == x.x << y == 0对所有位y >= 32.但是,如果将32位操作数移到32位或更多位置,编译器可以自由地做任何他们想做的事情,并且它们甚至可以自由地表现不一致(或者让恶魔飞出你的鼻子).
实现BIT_FIELD_MASK:
这将a通过位b(包括)设置位,只要0 <= a <= 31和0 <= b <= 31.
#define BIT_MASK(a, b) (((unsigned) -1 >> (31 - (b))) & ~((1U << (a)) - 1))
Run Code Online (Sandbox Code Playgroud)
移动大于或等于整数类型的大小是未定义的行为.
所以不,这不是GCC的错误.
在这种情况下,文字1的类型int在您使用的两个系统中都是32位.因此,移动32将调用此未定义的行为.
在第一种情况下,编译器无法将移位量解析为32.因此它可能只发出正常的移位指令.(在x86中只使用底部的5位)所以你得到:
(unsigned int)(1 << 0) - 1
Run Code Online (Sandbox Code Playgroud)
这是零.
在第二种情况下,GCC能够将移位量解析为32. 由于它是未定义的行为,它(显然)只是将整个结果替换为0:
(unsigned int)(0) - 1
Run Code Online (Sandbox Code Playgroud)
所以你得到了ffffffff.
因此,这是GCC使用未定义行为作为优化机会的情况.
(虽然我个人而言,我更愿意发出警告.)