【问题标题】:Is it safe to use an enum in a bit field?在位字段中使用枚举是否安全?
【发布时间】:2012-08-12 13:41:26
【问题描述】:

说,我有以下结构:

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 目前由#defines 定义,从0x000x0D

其实d3是一个枚举。所以很想继续替换

    unsigned int        d3  :4;

通过

    my_enum             d3  :4;

这是安全/允许的吗?

代码必须用各种编译

  • 编译器(GCC、Visual Studio、嵌入式)
  • 平台(Win32、Linux、嵌入式)
  • 配置(编译为 C,编译为 C++)

显然,我可以保留 d3 的定义并在我的代码中使用枚举,将其分配给 d3 等等,但这不适用于 C++。

【问题讨论】:

  • 你用多少个编译器试过这个?

标签: c++ c enums bit-fields


【解决方案1】:

在所有支持标准的 C++ 编译器中都允许。

C++03 标准 9.6/3

位域应具有整数或枚举类型(3.9.1)。它是 实现定义是普通(既不是显式签名也不是无符号)char、short、int 或 长位域有符号或无符号。

C++03 标准 9.6/4

如果一个 enu- 的值 merator存储在同一个枚举类型的位域中,并且位域中的位数很大 足以容纳该枚举类型的所有值,原始枚举值和位域的值应该比较相等。

例子

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,除非枚举的值 merator 不能放入 int 或 unsigned int 中

【讨论】:

    【解决方案2】:

    C 和 C++ 的答案会有所不同,这是 C 的答案。

    在 C 中,位域仅限于 signed intunsigned int_Boolint,在这种情况下可以是前两个中的任何一个。编译器实现者可以根据自己的喜好添加到该列表中,但需要记录他们支持的类型。

    所以回答您的问题,如果您想绝对确定您的代码可移植到所有 C 编译器,不,不可以使用 enum 类型。

    当前标准的相应段落为:

    位域的类型应为合格或不合格 _Bool 的版本,已签名 int、unsigned int 或其他一些实现定义的类型。它是 实现定义是否允许原子类型。

    【讨论】:

    • 我通常选择#ifdef __GNUC__ #define ENUMBF(type) __extension__ type #else #define ENUMBF(type) unsigned #endif。然后将ENUMBF(myenum) field:x; 放入我的结构中。代码不太好,但您不会错过重要的 gcc 警告,例如当您的位域太小时,因为同事向枚举添加了更多案例...
    【解决方案3】:

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

    6.5.2.1

    位域的类型应为合格或不合格 int、unsigned int 或signed int 之一的版本。是否 (可能是合格的)“普通” int 的高阶位位置 位字段被视为符号位是实现定义的。一种 位域被解释为一个整数类型,由 指定位数。

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

    【讨论】:

    • 这不是未定义的行为,而是违反了约束。它是标有“约束”的部分的一部分,“应”的使用通常标志着标准中的约束。因此,如果类型不正确,则需要编译器发出诊断。其次,您似乎引用了该标准的过时版本。请参阅我的答案以获得正确的版本(我在其中编辑过),它最终允许实现特定类型的发生。
    • 实际上 ISO/IEC 9899:1999 (E) 表明: 4. 一致性 1 在本国际标准中,“应”被解释为对实施或程序的要求;相反,“不应”将被解释为禁止 6.7.2.1 ... 4 位域的类型应为 _Bool、signed int、unsigned int 或其他一些实现的限定或非限定版本- 定义的类型。因此,允许实现定义的类型。 typedef enum {...} mytype_t; 适合。不过,我使用的编译器接受了这一点,但忽略了它的位域大小。
    • 除上述内容外,来自上述标准的 J.3.9 暗示“_Bool、signed int 和 unsigned int 以外的允许位字段类型”具有“实现定义的行为”。
    【解决方案4】:

    没有。

    位域在编译器之间的实现方式明显不同。如果你用两个值(0 和 1)定义一个位域,并尝试使用枚举类型的位域,那么你可能会遇到这些问题:

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

    那你就得担心打包了。如果它们的大小匹配,VC++ 只会将相邻的位字段打包到同一个后备存储中。我不确定 gcc 和 clang 的规则是什么,但对于 VC++,位字段的默认后备存储是 int。因此,一系列位字段(例如,bool 和 enum 的混合)在 VC++ 中的打包非常糟糕。

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

    枚举 Foo : unsigned char { 一,二 };

    但如果您在一位位字段中使用它,则 gcc 会抱怨:

    警告:“bitfieldTest::g”太小,无法容纳“enum Foo”的所有值[默认启用]

    好像没有中奖。

    【讨论】:

    • 有趣的是,对于enum class,出于某种原因,它要好得多:所有编译器都默认为int,可以在gcc中更改底层类型而不会发出警告。唯一的问题是 gcc 有一个错误,它曾经在很长一段时间内对enum class 的所有情况下都给出相同的错误警告。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-02-11
    • 1970-01-01
    • 2017-01-29
    • 2020-12-30
    • 1970-01-01
    相关资源
    最近更新 更多