Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unexpected behavior of bit-field

Tags:

c

I compiled the code,

#include <stdio.h>

struct s {
    int a : 6;
    _Bool b : 1;
    _Bool c : 1;
    _Bool d : 1;
    _Bool e : 1;
    _Bool f : 1;
    _Bool g : 1;
    int h : 12;
};

void main(void) {
    printf("%d\n", sizeof(struct s));
}

and the output was a bit unexpected.

12

As stated by the C11 draft,

... 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...

I expected it to fit into 4 bytes since I used a 32-bit compiler. Specifically, I used gcc (tdm-1) 5.1.0. Is it against the standard?


EDIT:

Replacing all the _Bools to an int works as expected... I'm not sure why...


EDIT:

In gcc 5.4.0, the code works as expected. The crucial point of this question is why the trailing _Bools and the int do not fit into the first one. I think I didn't do much assumptions about the implementation (except int is at least 4 bytes, which is acceptable), and I'm talking here about the guaranteed behavior of C by the C standard. Therefore, I cannot agree with some comments below.

like image 868
b1sub Avatar asked May 02 '17 10:05

b1sub


1 Answers

These are bit-fields. There is not much in the way of an "expected" output, as these are very poorly specified by the standard. In addition, compilers tend to have poor support for them.

To begin with, the complete section that you quote (6.7.2.1/11) says:

An implementation may allocate any addressable storage unit large enough to hold a bitfield. 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.

All of this means that you pretty much can make no assumptions about how the bits end up in memory. You can't know how the compiler will arrange alignment, you can't know the bit order of the bits, you can't know the signedness, you can't know the endianess.

As for if int and _Bool will merge into the same "storage unit"... well, why would they? They are incompatible types. The C standard does not mention what will happen when you have two adjacent bit fields of incompatible types - it is left open to subjective interpretation. I suspect that there will be various type aliasing concerns.

So it is perfectly fine for the compiler to put all of these in separate "storage units". If you place items of the same type adjacently, I would however expect it to merge them or the quoted part wouldn't make any sense. For example I would expect size 8 from the following:

struct s {
    int a : 6;
    int h : 12;
    _Bool b : 1;
    _Bool c : 1;
    _Bool d : 1;
    _Bool e : 1;
    _Bool f : 1;
    _Bool g : 1;
};

Now what you should do if you want deterministically behaving, portable code is to throw bit-fields out the window and use bit-wise operators instead. They are 100% deterministic and portable.

Assuming you actually don't want some mysterious signed number fields, which your original code hints about, then:

#define a UINT32_C(0xFC000000)
#define b (1u << 18)
#define c (1u << 17)
#define d (1u << 16)
#define e (1u << 15)
#define f (1u << 14)
#define g (1u << 13)
#define h UINT32_C(0x00000FFF)

typedef uint32_t special_thing;

Then design setter/getter functions or macros that sets/gets the data from this 32 bit chunk. Written properly, you can even make such code endianess-independent.

like image 90
Lundin Avatar answered Sep 20 '22 16:09

Lundin