Nils Hasenbanck is the founder of Tsukisoft GmbH and a senior developer. His passion is building technically elegant, easy to maintain …
Bitpacking fun
When porting a C API to Rust, it’s important to understand the underlying structure layout rules that the C compiler uses. One such rule is the use of bitpacking, which allows for compact storage of data by packing multiple fields into a single backing field.
We recently ported a C++ library with a C API to Rust. There we encountered a bitpacked structure, that looked like this:
struct BITPACKED {
uint8_t FieldA : 8;
uint64_t FieldB : 1;
uint64_t FieldC : 7;
uint64_t FieldD : 48;
};
The structure BITPACKED
contains four fields: FieldA
, FieldB
, FieldC
, and FieldD
. FieldA
is defined as an 8-bit unsigned integer. FieldB
, FieldC
, and FieldD
are all defined as 64-bit
unsigned integers.
Which size would you expect for this structure? My initial guess was 8 bytes, since the compiler could pack them into one 64-bit backing field.
But according to the C standard, bitpacking is implementation-defined, meaning that the compiler is free to choose how to layout the fields in memory. GCC and Clang will pack this structure into one 64-bit packing field.
But our target, the Microsoft Visual C++ (MSVC) compiler has a specific set of rules for bitpacking structures, that might suprise the reader (it also surprised the writer of the C++ library, who inteded to bitpack the structure into one 64-bit field).
MSVC will only bitpack fields that are of the same size into the same backing field. This means
that FieldA
is stored into one 8-bit backing field and FieldB
, FieldC
and FieldD
into
another 64-bit backing field. Since the second field needs to be aligned to 8 bytes, the resulting
structure will have a size of 16 bytes.
So the resulting structure in Rust looked something like this:
#[repr(C)]
pub struct BITPACKED {
FieldA: u8,
Packed: u64,
}
In conclusion, understanding the layout rules of bitpacked structures is crucial when porting a C API to Rust, as it can affect the size and alignment of the data in memory. Always check the documentation of the specific compiler you’re using, as the rules for bitpacking can vary between compilers.