Reputation: 11
I have the following layout of memory (pseudo code):
struct {
union {
fieldA : 45;
struct {
fieldB1 : 12;
fieldB2 : 33;
}
}
fieldC : 19;
}
i.e., the memory of field A can be sometimes used for other purposes (fields B1 and B2). I want this struct to be as packed as possble, i.e. 64bit in size.
It seems like no matter what I do (packed attributes for example) the union is always padded with 3 bits to get 48bits (6Bytes) before fieldC (which is also padded of course).
Upvotes: 1
Views: 127
Reputation: 162164
Within the scope of the C programming language, struct
s and union
s are actually not that well suited for creating precise memory layouts down to the bit level. Even with compiler specific packing extensions/pragmas, you still might end up with elements getting padded and aligned to meet certain requirements of the implementation. The only reliable way of doing bit level precise accesses in standard C is through (arrays of) primitive types and bit operations to mask out and extract/insert the desired values. And then there's also the whole endianess issue, which I'm going to lazily ignore here.
In your particular case, you could do it like this:
/* using a struct for encapsulation, to get some degree of static typing */
struct mybitfieldtype { uint64_t _; };
#define MYBITFIELD_ELEMENT(name, W, S) \
static inline uint64_t \
mybitfieldtype_get_##name( struct mybitfieldtype const v ) \
{ return (v._ >> (S)) & ((uint64_t)1<<(W))-1; } \
\
static inline struct mybitfieldtype \
mybitfieldtype_set_##name( struct mybitfieldtype const v, uint64_t const x ) \
{ return (struct mybitfieldtype) \
{ (v._ & ~(((uint64_t)1<<(W))-1<<(S))) \
| (x & (((uint64_t)1<<(W))-1))<<(S) }; }
MYBITFIELD_ELEMENT(fieldA, 45, 0)
MYBITFIELD_ELEMENT(fieldB1, 12, 0)
MYBITFIELD_ELEMENT(fieldB2, 33, 12)
MYBITFIELD_ELEMENT(fieldC, 19, 45)
Upvotes: 1
Reputation: 180048
Generally speaking, do not use bitfields. Certainly not in code that you want to be portable. Their semantics are much more loosely defined than the uninitiated tend to assume, and they can be surprising in multiple ways. Aspects that are important for some potential uses are unspecified or implementation-defined and do vary between implementations.
You can rely, however, on every object other than a bitfield to have a representation comprising a contiguous sequence of one or more (complete) bytes (C23 6.2.6.1/2).* Supposing, then, that your system's bytes are 8 bits wide, you cannot have a struct
or union
whose representation comprises exactly 45 bits, as 45 is not a multiple of 8. In your case, the inner struct will have a size of at least 6 bytes, so the union containing it must also be at least that large. The outer struct contains an additional member requiring 19 bits, so the overall struct must be at least 6 + 4 = 10 bytes in size.
My first recommendation would be my lead: don't use bitfields. For example,
struct foo {
union {
uint64_t fieldA;
struct {
uint16_t fieldB1 : 12;
uint64_t fieldB2 : 33;
};
};
uint32_t fieldC;
};
Of course, that does not achieve your objective of packing it into 64 bits, but C does not define any way to ensure such packing for a structure or union. Also, that's already leaning on implementation-defined behavior with respect to which type specifiers bitfield members may have.
You could consider a union of structs, such as @someprogrammerdude recommended. But as long as 64 bits is enough, you could also consider an ordinary packed integer, perhaps with supporting macros or functions:
typedef uint64_t my_fields;
#define FIELDA(mf) ((mf) >> 19)
#define SET_FIELDA(mf, v) do { \
my_fields *temp = &(mf); \
*temp = (*temp & (~(uint64_t)0 >> 45)) | (((uint64_t) (v)) << 19); \
} while (0)
// ...
Since you expressed some concern that you might need to be flexible toward changes in the data structure, wrapping accesses with macros or functions and abstracting the data type provide a great deal of flexibility for doing so.
* This appears to include C23 bit-precise integer types, so I guess those will including padding bits under many circumstances.
Upvotes: 2
Reputation: 409136
As a possible workaround, you need to duplicate some fields and make a union of two structures using all fields:
struct S {
union {
struct {
uint64_t fieldA : 45;
uint64_t fieldC : 19;
} a;
struct {
uint64_t fieldB1 : 12;
uint64_t fieldB2 : 33;
uint64_t fieldC : 19;
} b;
};
};
Upvotes: 4
Reputation: 140880
How to construct a C struct/union with a very odd arrangement of bitfields?
64bit in size.
Use a typedef or a struct with a single uint64_t
. Write getters and setters for every field using bitfield operations.
Upvotes: 5