用于创建位掩码的 C 宏——可能吗?我是否发现了 GCC 错误?

标签 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);
}


如果我用 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_MASKBIT_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 <= 310 <= b <= 31 .

#define BIT_MASK(a, b) (((unsigned) -1 >> (31 - (b))) & ~((1U << (a)) - 1))

关于用于创建位掩码的 C 宏——可能吗?我是否发现了 GCC 错误?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/8774567/

相关文章:

c - Fork() 定义变量

c - 未指定大小的数组声明如何充当指针?

javascript - javascript中>>>按位移位运算符的用途是什么?

c - 如何从双向链表中删除节点

c - libusb - 段错误 : 11

html - 如何在单个 csh/perl/python 脚本中将 20 个 .html 文件从 Linux 传输到 Windows?

c - 按位运算

c - 即使我的计算机是 Little Endian,以 Binary 打印数字总是以 Normal 格式(BIG Endian)打印?

c - 如何在C中获取整数的特定位段?

c - 如何将四个 16 位 uint 编码为 64 位 uint,然后再次解码它们?