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

时间:2015-11-07 20:42:35

标签: c language-lawyer c99

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

  

位字段的类型应为 _Bool signed int unsigned 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编译器一起使用是否安全?

2 个答案:

答案 0 :(得分:4)

  

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

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

正如您引用的标准部分所述,位字段必须是_Boolintunsigned int类型的某些实现 - 定义类型。

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对标准的解释的变化;这两种行为都是允许的。

enum用作位字段不可移植。符合标准的C编译器可能支持也可能不支持它们。 (如果gcc保留了对此发出警告的能力,我本人会更喜欢它。)

答案 1 :(得分:0)

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

如果你有:

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,因为它假定为{{1}会处理它。也许不是你想要的。

添加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;,您可以在其中控制大小[种类]。当使用标准的东西工作得很好时尝试应用半非便携式扩展没有任何好处。