Search code examples
cstructenumsbit-fields

Enum and unsigned long bitfields


If I have the following struct with an enum and unsigned long, how is the padding accomplished? I believe the compiler wont do anything with var1 and var2 since it is 32 bits and filled.

typedef struct {
    unsigned long var1 : 8;
    unsigned long var2 : 24;
    my_enum var3       : 2;
    my_enum var4       : 2;
} my_struct;

Would there be a padding for both var3 and var4? such as 30 bits of padding to var3 and 30 bits to var4, or 28 bits. I'm just trying to understand how bitfields work across different data types and implementation types.


Solution

  • 1. Types used in a bitfield:

    I would only use signed int or unsigned int in your stucture as bitfield type. According to the C99 standard (6.7.2.1 #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.

    Sure an unsigned long and enum type are implementation-defined types, but if you use them the behaviour is not portable. You can see it in 2 different GCC versions:

    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.

    2. Padding:

    Bitfields are working in word and byte level and cannot cross word boundaries. C99 guarentees that bitfields will be packed as tightly as possible, provided they don’t cross storage unit boundaries (6.7.2.1 #10).

    An implementation may allocate any addressable storage unit large enough to hold a bit- field. If enough space remains, a bit-field that immediately follows another bit-field in a structure shall be packed into adjacent bits of the same unit. If insufficient space remains, whether a bit-field that does not fit is put into the next unit or overlaps adjacent units is implementation-defined. The order of allocation of bit-fields within a unit (high-order to low-order or low-order to high-order) is implementation-defined. The alignment of the addressable storage unit is unspecified.

    If you you want to pad the exisiting bit fields in a unit up the the next unit you can do it with a length zero bitfield as said in the standard (6.7.2.1 #11):

    As a special case, a bit-field structure member with a width of 0 indicates that no further bit-field is to be packed into the unit in which the previous bit- field, if any, was placed.

    Further it says something about the end in C99 (6.7.2.1 #15):

    There may be unnamed padding at the end of a structure or union.


    What that means for your struct:

    You var1 and var2 will be stored together in 4 byte (32 bit) of memory without any gaps. Because bitfields can operate at byte level your var3 and var4 can also be stored together representing one byte. But the order of them is implementaion defined. If you put a zero length bit field in between of them the var4 would start at the next aligned unit. The end of your structure will may be padded up to 32 bit or 64 bit.


    Real code example with your struct:

    Because you can't take addresses of bitfields it is not so easy to analyse the bahaviour of bitfields, here is a little piece of code that I wrote to analyse it with a little workaround anyway.

    I set the values inside the structure with a bit pattern that is easy to recognize and print out every bit of the whole structure (total size with sizeof()). You can see the result on ideone, or below. Note that the used system uses little-endian format per structure variable, therefore the bytes are swapped in contrast to the humand readable form (big-endian format). Also the var3 and var4 are in a different order as the standard says that it is implementation defined.

    Output:

    sizeof(my_struct)     = 8
    sizeof(my_struct_var) = 8
    Byte 0:  1 0 0 0 0 0 0 1 
    Byte 1:  0 0 0 0 0 0 0 1 
    Byte 2:  0 0 0 0 0 0 0 0 
    Byte 3:  1 0 0 0 0 0 0 0 
    Byte 4:  0 0 0 0 1 1 1 0 
    Byte 5:  0 0 0 0 0 0 0 0 
    Byte 6:  0 0 0 0 0 0 0 0 
    Byte 7:  0 0 0 0 0 0 0 0 
    

    Your structure uses effectively the byte 0 to 3 and a half of byte 4. Because I compiled on a 64 bit machine and ideone too, the half byte 4 is padded up to byte 7. Also I recommend reading this guideline about structure padding and bitfields.

    Code:

    #include <stdio.h>
    #include <limits.h>
    
    #if CHAR_BIT != 8
    #error "unsupported char size"
    #endif
    
    typedef enum { ONE, TWO, THREE } my_enum;
    
    typedef struct
    {
       unsigned long var1 : 8;
       unsigned long var2 : 24;
       my_enum       var3 : 2;
       my_enum       var4 : 2;
    } my_struct;
    
    int main(void)
    {
       int idx;
       int bitIdx;
       my_struct my_struct_var;
    
       memset (&my_struct_var,
               0,
               sizeof(my_struct_var));
    
       printf("sizeof(my_struct)     = %lu\n", sizeof(my_struct));
       printf("sizeof(my_struct_var) = %lu\n", sizeof(my_struct_var));
    
       my_struct_var.var1 = 0x81;     /* 1000 0001                     */
       my_struct_var.var2 = 0x800001; /* 1000 0000 0000 0000 0000 0001 */
       my_struct_var.var3 = 0b10;     /* 10                            */
       my_struct_var.var4 = 0b11;     /* 11                            */
    
       for (idx = 0; idx < sizeof(my_struct_var); ++idx)
       {
          char * curByte = &my_struct_var;
          curByte += idx;
          printf("\nByte %d:  ", idx);
          for (bitIdx = 0; bitIdx < CHAR_BIT; ++bitIdx)
          {
             printf("%c ", ((*curByte & (1 << ((CHAR_BIT - 1) - bitIdx))) >> ((CHAR_BIT - 1) - bitIdx)) + '0');
          }
       }
    
       return 0;
    }