【问题标题】:Are enums as bitfields implementation-defined types?枚举是位域实现定义的类型吗?
【发布时间】:2016-02-08 19:48:48
【问题描述】:

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

位域的类型应为 _Boolsigned intunsigned int 或其他的限定或非限定版本其他实现定义的类型。

和 6.7.2.2 段。 4

每个枚举类型都应与char、有符号整数类型或无符号整数类型兼容。类型的选择是实现定义的,但应该能够表示枚举的所有成员的值。 ...

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

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 编译器一起使用是否安全?

【问题讨论】:

  • @Rhymoid 我不确定这是否真的是重复的。 AFAIK 是 unsigned char unsigned int 的子类型,而 enum 可以等效于 int。也可能是我误解了 6.7.2.2 段中的“与 [...] 整数类型兼容”。 4.
  • 第一个引号表示它的实现定义了允许将哪些其他类型用作位域

标签: c language-lawyer c99


【解决方案1】:

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

是的。

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

正如在您引用的标准部分中所说,位字段的类型必须为 _Boolintunsigned int一些实现定义的类型。 p>

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

根据manual for gcc 4.8.4

  • _Boolsigned intunsigned int 以外的允许位字段类型 (C99 6.7.2.1)。

在严格符合模式下不允许其他类型。

根据 gcc-5.2.0 的手册:

  • _Boolsigned intunsigned int 以外的允许位字段类型(C99 和 C11 6.7.2.1)。

其他整数类型,例如long int,以及枚举类型,即使在严格符合模式下也是允许的。

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

使用enums 作为位字段是不可移植的。符合标准的 C 编译器可能支持也可能不支持它们。 (如果 gcc 能够为此获得警告,我个人会更喜欢它。)

【讨论】:

    【解决方案2】:

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

    如果你有:

    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) {
        ...
    }
    

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

    “隐含契约”是 [在这种情况下] 枚举旨在成为一组整数。请注意,您还可以:

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

    而且,执行以下操作非常好:bval.mask |= M2;。将位域切片和切块留给int,您可以在其中控制大小[排序]。当使用标准的东西工作正常时,尝试应用半非便携式扩展没有任何优势。

    【讨论】:

      猜你喜欢
      • 2011-07-17
      • 2012-03-10
      • 2011-09-11
      • 2014-08-06
      • 1970-01-01
      • 1970-01-01
      • 2018-03-06
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多