我对创建一个宏来为设备寄存器生成位掩码(最多 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);
}
如果我用 gcc -O2 bits2.c -o bits2
编译,并在 Linux/x86_64 机器上运行它,我得到以下信息:
32 00000000
32 ffffffff
如果我采用相同的代码并在 Linux/MIPS(大端)机器上编译它,我会得到:
32 00000000
32 00000000
在 x86_64 机器上,如果我使用 gcc -O0 bits2.c -o bits2
,然后我得到:
32 00000000
32 00000000
如果我调整 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
假设BIT_MASK
和 BIT_FIELD_MASK
从 0 索引 (0-31) 开始操作。 BIT_FIELD_MASK
是从一个位范围创建一个掩码,即 8: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)
当然,这是一个有点危险的宏,因为它对其参数求值两次。这是使用 static inline
的好机会如果您一般使用 GCC 或 objective-c 99。
static inline unsigned bit_mask(int x)
{
return (x >= sizeof(unsigned) * CHAR_BIT) ?
(unsigned) -1 : (1U << x) - 1;
}
正如 Mysticial 指出的那样,将 32 位整数移位超过 32 位会导致实现定义未定义的行为。以下是移位的三种不同实现方式:
- 在 x86 上,只检查移位量的低 5 位,所以
x << 32 == x
. - 在 PowerPC 上,只检查移位量的低 6 位,所以
x << 32 == 0
但是x << 64 == x
. - 在 Cell SPU 上,检查所有位,所以
x << y == 0
对于所有y >= 32
.
但是,如果您将 32 位操作数移动 32 位或更多,编译器可以自由地做任何他们想做的事情,它们甚至可以自由地表现不一致(或者让恶魔飞出你的 Nose )。
实现 BIT_FIELD_MASK:
这将设置位 a
通位b
(含),只要0 <= a <= 31
和 0 <= b <= 31
.
#define BIT_MASK(a, b) (((unsigned) -1 >> (31 - (b))) & ~((1U << (a)) - 1))
关于用于创建位掩码的 C 宏——可能吗?我是否发现了 GCC 错误?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/8774567/