6
votes

I'm trying to better understand the C99 standard but now I'm confused about using enums as bitfields in structs and if they are treated as int or as implementation-defined type. When looking up in the final draft for C99, I found 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.

and 6.7.2.2 para. 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. ...

So I tried with this simple source code

enum e {
    E0, E1
};

struct s {
    enum e bitfield : 4;
};

I can compile this without warnings with gcc-5.0 and clang-3.5 using -std=c99 -Wall -Wextra -pedantic but I get the following warning with gcc-4.8

warning: type of bit-field 'bitfield' is a GCC extension

Here start the confusion. Are enums as bitfields treated as int or implemenation-defined type? Is this a bug in GCC-4.8 or did they change their interpretation of the standard? And is it safe to use this with other C99-compiler?

2
Possible duplicate of warning when using bitfield with unsigned charuser824425
@Rhymoid I'm not sure if this is really a duplicate. AFAIK are unsigned char a subtype of unsigned int whereas enum can be equivalent to int. It can also be that I misinterpret the "compatible with [...] integer type" in 6.7.2.2 para. 4.fsasm
The first quote means that it's implementation-defined which other types are allowed to be used as bitfieldsM.M

2 Answers

6
votes

Are enums as bitfields implementation-defined types?

Yes.

What you're seeing is a change in the implementation-defined behavior of gcc.

As it says in the section of the standard you quoted, a bit field must be of type _Bool, int, unsigned int, or some implementation-defined type.

A enum type is compatible with some integer type. Experiment and a look at the gcc manual shows that for gcc, your enum e is compatible with unsigned int. But the standard doesn't permit a bit field to be of a type compatible with unsigned int. It actually has to be of type unsigned int (compatible types are not necessarily the same type). Except that it can also be of some other implementation-defined type.

According to the 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.

According to the manual for 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.

So what you're seeing is a change in the behavior of gcc, allowing more types for bit fields even in "strictly conforming mode". This is not a change in gcc's interpretation of the standard; both behaviors are permitted.

Using enums as bit fields is not portable. A conforming C compiler may or may not support them. (I personally would have preferred it if gcc had kept the ability to get a warning for this.)

0
votes

It may be safe, but don't do it. You're violating an implied design-by-contract. Sort of. You mentioned the construct, but didn't mention how you really wanted to use it. There may be a cleaner way.

If you have:

typedef enum {
    E0, E1, E2
} myenum_t;

myenum_t val;

Now, if you have various switch statements, such as:

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

They will be checked at compile time to ensure your switch covered all the cases. If you then add an E3 to your enum definition, the compiler will flag the switch statements as missing E3. This is useful.

If you start bit diddling your enum value, you may have to change your switch to:

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

Now, if you add E3 to your enum, the compiler won't flag your switch for the missing case because it assumes default will handle it. Maybe not what you want.

Adding the default is usually there to debug errant enum values.

However, if you had:

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

mybit_t bval;

Using the following:

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

Is possibly a bit cleaner and maybe closer to what you really hope to achieve.

The "implied contract" is that [in this context] enums are intended to be a set of whole numbers. Note that you can also have:

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

And, it's perfectly fine to do something like: bval.mask |= M2;. Leave the bitfield slicing and dicing to int where you can control the size [sort of]. There's no advantage to trying to apply a semi-non-portable extension when using the standard stuff works just fine.