枚举是位域实现定义的类型吗?

Are enums as bitfields implementation-defined types?

我试图更好地理解 C99 标准,但现在我对将枚举用作结构中的位域以及它们是被视为 int 还是实现定义的类型感到困惑。在查找 C99 的最终草案时,我找到了 6.7.2.1 para。 4

A bit-field shall have a type that is a qualified or unqualified version of _Bool, signed int, unsigned int, or some other implementation-defined type.

和 6.7.2.2 段。 4

Each enumerated type shall be compatible with char, a signed integer type, or an unsigned integer type. The choice of type is implementation-defined, but shall be capable of representing the values of all the members of the enumeration. ...

所以我尝试使用这个简单的源代码

enum e {
    E0, E1
};

struct s {
    enum e bitfield : 4;
};

我可以使用 -std=c99 -Wall -Wextra -pedantic 使用 gcc-5.0 和 clang-3.5 在没有警告的情况下编译它,但是我使用 gcc-4.8

得到以下警告
warning: type of bit-field 'bitfield' is a GCC extension

这里开始混乱。作为位域的枚举是否被视为 int 或实现定义的类型?这是 GCC-4.8 中的错误还是他们改变了对标准的解释?将它与其他 C99 编译器一起使用是否安全?

Are enums as bitfields implementation-defined types?

是的。

您看到的是 gcc 的实现定义行为的变化。

正如您引用的标准部分所述,位域的类型必须为 _Boolintunsigned int 一些实现定义的类型。

A enum 类型与某些整数类型兼容。实验并查看 gcc 手册表明,对于 gcc,您的 enum eunsigned int 兼容。但是该标准不允许位域是 compatible with unsigned int 类型。它实际上必须是 unsigned int 类型(兼容类型不一定是同一类型)。 除了它也可以是其他一些实现定义的类型。

根据 manual for gcc 4.8.4:

  • Allowable bit-field types other than _Bool, signed int, and unsigned int (C99 6.7.2.1).

No other types are permitted in strictly conforming mode.

根据 gcc-5.2.0 的手册:

  • Allowable bit-field types other than _Bool, signed int, and unsigned int (C99 and C11 6.7.2.1).

Other integer types, such as long int, and enumerated types are permitted even in strictly conforming mode.

因此,您看到的是 gcc 行为的变化,即使在“严格符合模式”下,也允许位字段使用更多类型。这不是 gcc 对标准的解释的改变;这两种行为都是允许的。

使用 enums 作为位字段是不可移植的。符合规范的 C 编译器可能支持也可能不支持它们。 (如果 gcc 保留对此发出警告的能力,我个人会更喜欢它。)

它可能是安全的,但不要这样做。您违反了隐含的合同设计。有点。你提到了这个结构,但没有提到你真正想如何使用它。可能有更清洁的方法。

如果你有:

typedef enum {
    E0, E1, E2
} myenum_t;

myenum_t val;

现在,如果你有各种 switch 语句,例如:

switch (val) {
case E0:
    ...
    break;
case E1:
    ...
    break;
case E2:
    ...
    break;
}

它们将在编译时进行检查,以确保您的 switch 涵盖所有情况。如果随后将 E3 添加到枚举定义中,编译器会将 switch 语句标记为缺少 E3。这个有用。

如果您开始使用枚举值,您可能需要将 switch 更改为:

switch (val) {
case E0:
    ...
    break;
case E1:
    ...
    break;
case E2:
    ...
    break;
default:
    ...
    break;
}

现在,如果您将 E3 添加到您的枚举中,编译器 将不会 将您的 switch 标记为缺少的 case 因为它假设 default 会处理它。也许不是你想要的。

添加 default 通常用于调试错误的枚举值。

但是,如果您有:

typedef struct {
    unsigned int mask:8;
    unsigned int enval:8;
    unsigned int ident:8;
    unsigned int other:8;
} mybit_t;

mybit_t bval;

使用以下内容:

switch ((myenum_t) bval.enval) {
    ...
}

可能更简洁一些,可能更接近您真正希望实现的目标。

"implied contract" 是 [在这种情况下] 枚举旨在成为一组整数。请注意,您还可以:

typedef enum {
    M0 = 1 << E0, M1 = 1 << E1, M2 = 1 << E2
} mymask_t;

而且,做这样的事情是完全没问题的:bval.mask |= M2;。将位域切片和切块留给 int,您可以在其中控制大小 [排序]。当使用标准的东西工作得很好时,尝试应用半不可移植的扩展没有任何优势。