C宏创建一个掩码 - 可能吗?我找到了一个GCC错误吗?

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_MASKBIT_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位会导致实现定义的未定义行为.以下是三种不同的转换实现:

  • 在x86上,只检查移位量的低5位,所以x << 32 == x.
  • 在PowerPC上,只检查移位量的低6位,x << 32 == 0但是x << 64 == x.
  • 在Cell SPU上,检查所有位,x << y == 0对所有位y >= 32.

但是,如果将32位操作数移到32位或更多位置,编译器可以自由地做任何他们想做的事情,并且它们甚至可以自由地表现不一致(或者让恶魔飞出你的鼻子).

实现BIT_FIELD_MASK:

这将a通过位b(包括)设置位,只要0 <= a <= 310 <= b <= 31.

#define BIT_MASK(a, b) (((unsigned) -1 >> (31 - (b))) & ~((1U << (a)) - 1))
Run Code Online (Sandbox Code Playgroud)


Mys*_*ial 8

移动大于或等于整数类型的大小是未定义的行为.
所以不,这不是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使用未定义行为作为优化机会的情况.
(虽然我个人而言,我更愿意发出警告.)

相关:为什么带有GCC的x86上的整数溢出会导致无限循环?

  • 应该是"超过或等于",而不是"超过". (2认同)