How can I verify the layout of a (repr(C)
) structure without running the code? E.g. when I have
#[repr(C)]
struct Registers {
urxd: u32, // 0x00
_rsrvd0: [u32;15],
utxd: u32, // 0x40
_rsrvd1: [u32;15],
ucr1: u32, // 0x80
}
how can I make the build process fail when ucr1
is not at positition 0x80 (e.g. due to miscalculated _rsrvd
members or target depending padding)?
In C I would write something like
struct foo {
uint32_t a;
uint32_t b;
uint32_t c;
uint32_t d;
};
static void _test() {
_Static_assert(offsetof(struct foo, d) == 12);
}
For _Static_assert
there seem to exist crates like static_assertions
which implement hacks like these from the good old C times (negative array sizes and so).
But for offsetof()
I have found only non-const
implementations.
Code is for embedded platforms without #[test]
support so I can not test it at runtime. Running #[test]
on a std-platform might give wrong results because padding/alignment is different there.
CodePudding user response:
You can use the const_field_offset
crate to get the offset and the static_assertions
to assert during the build.
use const_field_offset;
use static_assertions as sa;
#[repr(C)]
#[derive(const_field_offset::FieldOffsets)]
struct Registers {
urxd: u32, // 0x00
_rsrvd0: [u32;15],
utxd: u32, // 0x40
_rsrvd1: [u32;15],
ucr1: u32, // 0x80
}
sa::const_assert!(0x80 == Registers::FIELD_OFFSETS.ucr1.get_byte_offset());
When the assert fails, the error message isn't super helpful, but it does at least fail the build:
sa::const_assert!(0x79 == Registers::FIELD_OFFSETS.ucr1.get_byte_offset());
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ attempt to compute `0_usize - 1_usize`, which would overflow
CodePudding user response:
If you want a nicer error message you can roll your own with a little unsafe
.
const _ASSERT_OFFSET:() = {
let val = Registers{
urxd:0,
_rsrvd0:[0;15],
utxd:0,
_rsrvd1:[0;15],
ucr1:1,
};
let ptr = &val as *const Registers as *const u8;
let field_ptr = unsafe{ ptr.add(0x80) } as *const u32;
if unsafe{ *field_ptr } != 1{
panic!("ucr1 was at the wrong offset")
}
};
The way this works is pretty simple: write a known value into ucr1
, and then read whatever value is in what's supposed to be ucr1
. If they match, every things fine, and if they don't, you get a message like
error[E0080]: evaluation of constant value failed
--> src/lib.rs:22:9
|
22 | panic!("ucr1 was at the wrong offset")
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the evaluated program panicked at 'ucr1 was at the wrong offset', src/lib.rs:22:9
|
= note: this error originates in the macro `$crate::panic::panic_2021` (in Nightly builds, run with -Z macro-backtrace for more info)
Bear in mind that if the read goes out of bounds you will get a different, more opaque, error from rustc.