On 08/11/18 00:51, Keith Thompson wrote:
> David Brown <
david...@hesbynett.no> writes:
> [...]
>> No, it is certainly not wrong according to the C (or C++) standards, nor
>> is it wrong according to the ABI's of many processors and C
>> implementations. And speaking as someone who uses bitfields regularly
>> in cases where exact layouts and accesses are important, it is often
>> /essential/ that the type of the bitfield be used for the size,
>> alignment and accesses.
>>
>> It is extremely simple to get the layout you want - use an appropriately
>> sized underlying type. (Yes, support for that is
>> implementation-dependant - but so are most things regarding bit-fields.)
>
> In C90, the only permitted underlying types for bit fields were
> int, signed int, and unsigned int, so it wasn't possible to use
> the underlying type to control the layout. (C99 added _Bool bit
> fields, and many implementations permit other integer types.)
Yes, I realised that when someone else posted it. Many of the compilers
I have used over the years have supported different bit-field types as
extensions even in C90 modes - even some that had little or no C99
support. In my line of work, high portability is not often important,
and use of compiler extensions are often required. (Plus my only copy
of C90 is scanned pdf, so it is vastly easier to look things up in C99
or C11 standards and try to forget C90 ever existed...)
> My understanding was always that defining
> unsigned int bf:5;
> simply meant that bf would occupy 5 bits and be able to store values
> in the range 0..31. Permitting, say, unsigned long or unsigned
> long long (as some C implementations do, and as C++ requires) means
> that you can have bit fields wider than an int, but it would never
> have occurred to me that changing the underlying type for the bit
> field itself would affect the layout of the structure outside the
> bit field.
>
> I understand and accept that it's conventional to use the underlying
> type that way, and I suppose I wouldn't object to a future C or C++
> standard codifying that behavior, but I've always found it odd.
Fair enough - I can understand that. Equally, I find it odd if the
underlying type is /not/ used that way. I suspect this is a matter of
what we are used to, what the tools we most use do, and where we use
bit-fields. What is certain, is that bit-fields are not particularly
portable. The semantics of them for storing data is of course portable,
but the exact layout details is not. When you need exact layout to be
portable, bit masks, shifts, and fixed-width integer types is the way to go.
As I see it, there are three main uses of bit-fields.
1. For use within a program, to pack data more tightly. The exact
layout details don't matter, and bit-fields work fine regardless of the
rules used by the implementation.
2. For matching pre-defined data, such as in network protocols or file
formats. Here you do need exact matching of layout, but access sizes
don't matter. So if you have:
struct X {
uint32_t a : 5;
uint32_t b : 7;
uint32_t a : 4;
}
then you will need to know if X takes 2 bytes, 3 bytes or 4 bytes, and
you need to know the endianness. But you don't care if reading "x.a" is
done as an 8-bit read, a 16-bit read, or a 32-bit read (assuming there
is valid memory after the X). And you'd be equally happy with
"uint16_t" instead of "uint32_t" as the type.
3. For matching hardware registers, for device drivers or low-level
embedded code. Here you need more - you are usually using volatile
accesses, and if you say the bit-field type is "uint32_t" in X, then the
compiler must generate a 32-bit read and write instruction and not 8-bit
instructions.
>
> If I wanted to control the layout in that manner, I'd probably find
> it clearer to add unused members after the bit field for padding.
>
Agreed. (I do that in normal structs too, and use -Wpadded in gcc to
spot any missing padding. It is often not essential, but I like to have
the padding explicit rather than implicit.)