在位字段中使用枚举是否安全?

33

假设我有以下结构体:

typedef struct my_struct{
    unsigned long       a;
    unsigned long       b;
    char*               c;
    unsigned int        d1  :1;
    unsigned int        d2  :4;
    unsigned int        d3  :4;
    unsigned int        d4  :23;
} my_type, *p_type;

字段d3当前是由从0x000x0D#define定义的。

实际上,d3是一个枚举类型。因此,很容易就想着去替换这些#define为枚举值,但这可能会使得二进制兼容性受到影响。

    unsigned int        d3  :4;

    my_enum             d3  :4;

这样做安全/被允许吗?

代码必须与多种编译器(GCC、Visual Studio、嵌入式设备)、平台(Win32、Linux、嵌入式设备)和配置(编译为C、编译为C++)一起编译。

  • 显然,我可以保留 d3 的定义,并在我的代码中使用枚举类型,将其分配给 d3 等等,但这种方法无法在C ++中工作。

你用过多少个编译器来尝试这个? - HonkyTonk
4个回答

25

所有支持标准的C++编译器都允许这种写法。

C++03标准9.6/3

位域必须是整数或枚举类型(3.9.1)。对于未明确指定为有符号或无符号的char、short、int或long位域,其具体实现是由编译器定义的。

C++03标准9.6/4

如果将枚举类型的值存储到相同枚举类型的位域中,并且位域中的位数足以容纳该枚举类型的所有值,则原始枚举值和位域的值应当相等。

例子

enum BOOL { f=0, t=1 };

struct A {
    BOOL b:1;
};

void f() {
    A a;
    a.b = t;
    a.b == t // shall yield true
}

但是你不能认为枚举类型具有无符号的基础类型。

C++03标准7.2/5

枚举类型的基础类型是一个整数类型,可以表示定义在枚举中的所有枚举值。除非枚举值不能适合int或unsigned int,否则使用哪种整数类型作为枚举的基础类型是由实现定义的。


这是一个旧答案,所以我只想指出自从C++11以来,您可以为枚举指定(整数)基础类型。 - Spencer

17

对于 C 和 C++,答案会有所不同。这是针对 C 的回答。

C 中的位域被限制为 signed intunsigned int_Boolint,在此上下文中可以是前两者中的任何一种。编译器实现者可以根据自己的喜好添加到该列表中,但必须记录他们支持的类型。

因此,要回答您的问题,如果您想绝对确保您的代码可移植到所有 C 编译器,那么使用枚举类型不是一个选项。

当前标准的相应段落如下:

位域必须具有限定或未限定版本的 _Bool、signed int、unsigned int 或其他某种实现定义的类型。是否允许原子类型是由实现定义的。


2
通常我会选择 #ifdef __GNUC__ #define ENUMBF(type) __extension__ type #else #define ENUMBF(type) unsigned #endif 这个代码,并在我的结构体中使用 ENUMBF(myenum) field:x;。虽然代码看起来不那么漂亮,但你不会错过重要的 GCC 警告,例如当你的位域过小时,因为同事向枚举添加了更多情况... - MatzeBraun

9

编号.

不同编译器实现的位域有很大不同。如果您使用枚举类型的位域定义了两个值(零和一),则可能遇到以下问题:

使用 gcc 和 clang,位域将是无符号的,但使用 VC++ 则为有符号。这意味着为存储零和一,您需要一个两位的位域(一个有符号的一位位域只能存储零和负一)。

然后您需要担心打包。如果相邻的位域大小不匹配,则 VC++ 只会将相邻的位域打包到相同的支持存储器中。我不确定 gcc 和 clang 的规则是什么,但对于 VC++,位域的默认支持存储器为 int。因此,一个由布尔型和枚举型混合组成的位域系列在 VC++ 中可能会被打包得非常糟糕。

您可以尝试使用 C++ 11 类型化枚举来解决这个问题:

enum Foo : unsigned char { one, two };

但是,如果您在一位位域中使用它,则 gcc 将会发出警告:

warning: ‘bitfieldTest::g’ is too small to hold all values of ‘enum Foo’ [enabled by default]

看起来没有赢家。


有趣的是,对于 enum class 来说,某种原因上它要好得多:所有编译器默认为 int,在 gcc 中底层类型可以在没有警告的情况下更改。唯一的问题是,gcc 以前曾经有一个错误,在相当长的时间内对于 enum class 所有情况都给出了同样错误的警告。 - Predelnik
2
现在这已经过时了; gcc修复了执行此操作的错误。现在可以将两个值的枚举放入一个一位比特域中。 - Spencer

-1

在 C 语言中,这是一种未定义的行为,因为位域只能有 signed intintunsigned int 类型(或者在 C99 中使用 _Bool)。

6.5.2.1

一个位域必须具有 int、unsigned int 或 signed int 的限定或非限定版本。对于(可能限定的)“普通” int 位域的高位位置是否被视为符号位是实现定义的。位域被解释为由指定位数组成的整数类型。

否则,一些编译器今天将其作为扩展接受(参见标准中扩展的实现定义行为)。


4
这不是未定义行为,而是约束条件违反。它属于一个标记为“约束条件”的部分,“应当”一般用于标记标准中的约束条件。因此,如果类型不正确,编译器需要发出诊断报告。其次,你引用了看似过时的标准版本。请看我回答中的正确版本(我已经编辑),其中明确允许出现实现特定的类型。 - Jens Gustedt
实际上,ISO/IEC 9899:1999(E) 显示:
  1. 符合性
1 在本国际标准中,“应当”被解释为对实现或程序的要求;相反,“不得”被解释为禁止。6.7.2.1 ... 4 位域必须具有限定或未限定版本的_Bool、signed int、unsigned int或其他某些实现定义类型的类型。因此,允许使用实现定义类型。typedef enum {...} mytype_t; 就适用于此情况。然而,我正在使用的编译器接受它,但忽略了其位域大小。
- le_top
除上述之外,上面提到的标准中的J.3.9意味着“允许的位域类型除了_Bool、signed int和unsigned int之外”具有“实现定义的行为”。 - le_top

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接